Merging upstream version 2.1.1.
This commit is contained in:
parent
cad63e607f
commit
1b75311cb8
200
CHANGES
200
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)
|
0.27.0 (2017-01-28)
|
||||||
-------------------
|
-------------------
|
||||||
- Add support for SOAP attachments (multipart responses). (Dave Wapstra, #302)
|
- Add support for SOAP attachments (multipart responses). (Dave Wapstra, #302)
|
||||||
|
@ -11,13 +127,13 @@
|
||||||
This release again introduces some backwords incompatibilties. The next release
|
This release again introduces some backwords incompatibilties. The next release
|
||||||
will hopefully be 1.0 which will introduce semver.
|
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
|
``requests.Session()`` object instead of ``http_auth`` and ``verify``. This
|
||||||
allows for more flexibility.
|
allows for more flexibility.
|
||||||
- **backwards-incompatible**: Zeep no longer sets a default cache backend.
|
- **backwards-incompatible**: Zeep no longer sets a default cache backend.
|
||||||
Please see http://docs.python-zeep.org/en/master/transport.html#caching for
|
Please see http://docs.python-zeep.org/en/master/transport.html#caching for
|
||||||
information about how to configure a cache.
|
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.
|
element.
|
||||||
- Support duplicate target namespaces in the wsdl definition (#320)
|
- Support duplicate target namespaces in the wsdl definition (#320)
|
||||||
- Fix resolving element/types for xsd schema's with duplicate tns (#319)
|
- 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)
|
type. Instead log a message (#273)
|
||||||
- Fix serializing etree.Element instances in the helpers.serialize function
|
- Fix serializing etree.Element instances in the helpers.serialize function
|
||||||
(#255)
|
(#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)
|
unbounded (#256)
|
||||||
- Make the xsd.Element name kwarg required
|
- 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)
|
complexType's (#252)
|
||||||
- Silently ignore unsupported binding transports instead of an hard error
|
- Silently ignore unsupported binding transports instead of an hard error
|
||||||
(#277)
|
(#277)
|
||||||
- Support microseconds for xsd.dateTime and xsd.Time (#280)
|
- Support microseconds for xsd.dateTime and xsd.Time (#280)
|
||||||
- Don't mutate passed values to the zeep operations (#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
|
- Add Client.set_default_soapheaders() to set soapheaders which are to be used
|
||||||
on all operations done via the client object.
|
on all operations done via the client object.
|
||||||
- Add basic support for asyncio using aiohttp. Many thanks to chrisimcevoy
|
- 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/207 and
|
||||||
https://github.com/mvantellingen/python-zeep/pull/251 for more information
|
https://github.com/mvantellingen/python-zeep/pull/251 for more information
|
||||||
- Fix recursion error when generating the call signature (jaceksnet, #264)
|
- 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)
|
0.22.1 (2016-11-22)
|
||||||
-------------------
|
-------------------
|
||||||
- Fix reversed() error (jaceksnet) (#260)
|
- 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.
|
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
|
- Force the soap:address / http:address to HTTPS when the wsdl is loaded from
|
||||||
a https url (#228)
|
a https url (#228)
|
||||||
- Improvements to the xsd:union handling. The matching base class is now used
|
- 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)
|
then the raw value is returned. (#195)
|
||||||
- Fix handling of xsd:any with maxOccurs > 1 in xsd:choice elements (#253)
|
- 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)
|
http://www.w3.org/XML/1998/namespace (#220)
|
||||||
- Add new Client.type_factory(namespace) method which returns a factory to
|
- 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)
|
- 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)
|
- 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).
|
instead (#243).
|
||||||
- Fix SOAP arrays by wrapping children in the appropriate element
|
- Fix SOAP arrays by wrapping children in the appropriate element
|
||||||
(joeribekker, #236)
|
(joeribekker, #236)
|
||||||
- Add ``operation_timeout`` kwarg to the Transport class to set timeouts for
|
- Add ``operation_timeout`` kwarg to the Transport class to set timeouts for
|
||||||
operations. The default is still no timeout (#140)
|
operations. The default is still no timeout (#140)
|
||||||
- Introduce client.options context manager to temporarily override various
|
- Introduce client.options context manager to temporarily override various
|
||||||
options (only timeout for now) (#140)
|
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)
|
instead of throwing an exception (#137)
|
||||||
- Fix xsd:choice xml rendering with nested choice/sequence structure (#221)
|
- Fix xsd:choice xml rendering with nested choice/sequence structure (#221)
|
||||||
- Correctly resolve header elements of which the message part defines the
|
- 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)
|
0.20.0 (2016-10-24)
|
||||||
-------------------
|
-------------------
|
||||||
- Major performance improvements / lower memory usage. Zeep now no longer
|
- 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.
|
modified data.
|
||||||
- Fix parsing empty soap response (#223)
|
- Fix parsing empty soap response (#223)
|
||||||
- Major refactor of the xsd:extension / xsd:restriction implementation.
|
- 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
|
- **backwards-incompatible**: If the WSDL defines that the endpoint returns
|
||||||
soap:header elements and/or multple soap:body messages then the return
|
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.
|
body and header elements.
|
||||||
- Fix parsing HTTP bindings when there are no message elements (#185)
|
- Fix parsing HTTP bindings when there are no message elements (#185)
|
||||||
- Fix deserializing RPC responses (#219
|
- Fix deserializing RPC responses (#219
|
||||||
- Add support for SOAP 1.2 Fault subcodes (#210, vashek)
|
- 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)
|
deepcopy first. (#188)
|
||||||
- Add the SOAPAction to the Content-Type header in SOAP 1.2 bindings (#211)
|
- 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)
|
- 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)
|
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)
|
instead of the namespace map of the wsdl. (#184, #164)
|
||||||
- Improve handling of nested choice elements (choice>sequence>choice)
|
- 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 support for xsd:notation (#183)
|
||||||
- Add improvements to resolving phase so that all objects are resolved.
|
- Add improvements to resolving phase so that all objects are resolved.
|
||||||
- Improve implementation of xsd.attributeGroup and xsd.UniqueType
|
- 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.
|
original are unmodified.
|
||||||
- Improve handling of wsdl:arrayType
|
- Improve handling of wsdl:arrayType
|
||||||
|
|
||||||
|
|
||||||
0.16.0 (2016-09-06)
|
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
|
see #150
|
||||||
- Re-use credentials passed to python -mzeep <wsdl> (#130)
|
- Re-use credentials passed to python -mzeep <wsdl> (#130)
|
||||||
- Workaround invalid usage of qualified vs non-qualified element tags in the
|
- 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)
|
0.14.0 (2016-08-03)
|
||||||
-------------------
|
-------------------
|
||||||
- Global attributes are now always correctly handled as qualified. (#129)
|
- 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)
|
- Set xsi:nil attribute when serializing objects to xml (#141)
|
||||||
- Fix rendering choice elements when the element is mixed with other elements
|
- Fix rendering choice elements when the element is mixed with other elements
|
||||||
in a sequence (#150)
|
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.
|
exception. This better matches with what lxml does.
|
||||||
- **backwards-incompatible**: The ``persistent`` kwarg is removed from the
|
- **backwards-incompatible**: The ``persistent`` kwarg is removed from the
|
||||||
SqliteCache.__init__() call. Use the new InMemoryCache() instead when you
|
SqliteCache.__init__() call. Use the new InMemoryCache() instead when you
|
||||||
don't want to persist data. This was required to make the SqliteCache
|
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
|
backend thread-safe since we now open/close the db when writing/reading
|
||||||
from it (with an additional lock).
|
from it (with an additional lock).
|
||||||
- Fix zeep.helpers.serialize_object() for nested objects (#123)
|
- Fix zeep.helpers.serialize_object() for nested objects (#123)
|
||||||
- Remove fallback between soap 1.1 and soap 1.2 namespaces during the parsing
|
- 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)
|
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
|
maxOccurs=1. This results in easier operation definitions when choices are
|
||||||
used.
|
used.
|
||||||
- **backwards-incompatible**: The _soapheader kwarg is renamed to _soapheaders
|
- **backwards-incompatible**: The _soapheader kwarg is renamed to _soapheaders
|
||||||
and now requires a nested dictionary with the header name as key or a list
|
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>``.
|
call signature of the function using ``python -mzeep <wsdl>``.
|
||||||
- Support the element ref's to xsd:schema elements.
|
- Support the element ref's to xsd:schema elements.
|
||||||
- Improve the signature() output of element and type definitions
|
- Improve the signature() output of element and type definitions
|
||||||
- Accept lxml.etree.Element objects as value for Any elements.
|
- Accept lxml.etree.Element objects as value for Any elements.
|
||||||
- And various other fixes
|
- And various other fixes
|
||||||
|
|
||||||
|
|
||||||
0.11.0 (2016-07-03)
|
0.11.0 (2016-07-03)
|
||||||
-------------------
|
-------------------
|
||||||
- **backwards-incompatible**: The kwarg name for Any and Choice elements are
|
- **backwards-incompatible**: The kwarg name for Any and Choice elements are
|
||||||
renamed to generic ``_value_N`` names.
|
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
|
Client.create_service() call
|
||||||
- Auto-load the http://schemas.xmlsoap.org/soap/encoding/ schema if it is
|
- 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
|
referenced but not imported. Too many XSD's assume that the schema is always
|
||||||
available.
|
available.
|
||||||
- Major refactoring of the XSD handling to correctly support nested
|
- Major refactoring of the XSD handling to correctly support nested
|
||||||
xsd:sequence elements.
|
xsd:sequence elements.
|
||||||
- Add ``logger.debug()`` calls around Transport.post() to allow capturing the
|
- Add ``logger.debug()`` calls around Transport.post() to allow capturing the
|
||||||
content send/received from the server
|
content send/received from the server
|
||||||
- Add proper support for default values on attributes and elements.
|
- 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)
|
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
|
parsing. Previously the lookups where non-transitive, but this should only
|
||||||
be the case during parsing of the xml schema.
|
be the case during parsing of the xml schema.
|
||||||
- Properly unwrap XML responses in soap.DocumentMessage when a choice is the
|
- Properly unwrap XML responses in soap.DocumentMessage when a choice is the
|
||||||
root element. (#80)
|
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.
|
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)
|
- Quote the SOAPAction header value (Derek Harland)
|
||||||
- Undo fallback for SOAPAction if it is empty (#83)
|
- Undo fallback for SOAPAction if it is empty (#83)
|
||||||
|
|
||||||
|
|
||||||
0.9.0 (2016-06-14)
|
0.9.0 (2016-06-14)
|
||||||
------------------
|
------------------
|
||||||
- Use the appdirs module to retrieve the OS cache path. Note that this results
|
- 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.
|
https://github.com/ActiveState/appdirs for more information.
|
||||||
- Fix regression when initializing soap objects with invalid kwargs.
|
- Fix regression when initializing soap objects with invalid kwargs.
|
||||||
- Update wsse.UsernameToken to set encoding type on nonce (Antonio Cuni)
|
- 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)
|
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)
|
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()`
|
- Add ability to override the soap endpoint via `Client.set_address()`
|
||||||
- Fix parsing ComplexTypes which have no child elements (#50)
|
- 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)
|
responses (#17)
|
||||||
- Fix xsd:restriction on xsd:simpleType's when the base type wasn't defined
|
- Fix xsd:restriction on xsd:simpleType's when the base type wasn't defined
|
||||||
yet. (#59)
|
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
|
- Add support HTTP authentication (mcordes). This adds a new attribute to the
|
||||||
Transport client() which passes the http_auth value to requests. (#31)
|
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.
|
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)
|
lookup values in child definitions. (#40)
|
||||||
- Remove unused namespace declarations from the generated SOAP messages.
|
- Remove unused namespace declarations from the generated SOAP messages.
|
||||||
- Update requirement of six>=1.0.0 to six>=1.9.0 (#39)
|
- 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>
|
- Handle attributes during parsing of the response values>
|
||||||
- Don't create empty soap objects when the root element is empty.
|
- 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.
|
passwordText/passwordDigest.
|
||||||
- Improve XSD date/time related builtins.
|
- 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 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
|
- Use `application/soap+xml` as content-type in the Soap 1.2 binding
|
||||||
- **backwards incompatible**: Make cache part of the transport object
|
- **backwards incompatible**: Make cache part of the transport object
|
||||||
instead of the client. This changes the call signature of the Client()
|
instead of the client. This changes the call signature of the Client()
|
||||||
class. (Marek Wywiał)
|
class. (Marek Wywiał)
|
||||||
- Add the `verify` kwarg to the Transport object to disable ssl certificate
|
- Add the `verify` kwarg to the Transport object to disable ssl certificate
|
||||||
verification. (Marek Wywiał)
|
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
|
- Improve xsd.DateTime, xsd.Date and xsd.Time implementations by using the
|
||||||
isodate module.
|
isodate module.
|
||||||
- Implement xsd.Duration
|
- Implement xsd.Duration
|
||||||
|
|
||||||
|
|
||||||
0.2.3 (2016-04-03)
|
0.2.3 (2016-04-03)
|
||||||
|
|
|
@ -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
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -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
|
16
PKG-INFO
16
PKG-INFO
|
@ -1,6 +1,6 @@
|
||||||
Metadata-Version: 1.1
|
Metadata-Version: 1.1
|
||||||
Name: zeep
|
Name: zeep
|
||||||
Version: 0.27.0
|
Version: 2.1.1
|
||||||
Summary: A modern/fast Python SOAP client based on lxml / requests
|
Summary: A modern/fast Python SOAP client based on lxml / requests
|
||||||
Home-page: http://docs.python-zeep.org
|
Home-page: http://docs.python-zeep.org
|
||||||
Author: Michael van Tellingen
|
Author: Michael van Tellingen
|
||||||
|
@ -57,18 +57,16 @@ Description: ========================
|
||||||
Support
|
Support
|
||||||
=======
|
=======
|
||||||
|
|
||||||
If you encounter bugs then please `let me know`_ . A copy of the WSDL file if
|
If you want to report a bug then please first read
|
||||||
possible would be most helpful.
|
http://docs.python-zeep.org/en/master/reporting_bugs.html
|
||||||
|
|
||||||
I'm also able to offer commercial support. As in contracting work. Please
|
I'm also able to offer commercial support. As in contracting work. Please
|
||||||
contact me at info@mvantellingen.nl for more information. If you just have a
|
contact me at info@mvantellingen.nl for more information. Note that asking
|
||||||
random question and don't intent to actually pay me for my support then please
|
questions or reporting bugs via this e-mail address will be ignored. Pleae use
|
||||||
DO NOT email me at that e-mail address but just use stackoverflow or something..
|
the appropriate channels for that (e.g. stackoverflow)
|
||||||
|
|
||||||
.. _let me know: https://github.com/mvantellingen/python-zeep/issues
|
|
||||||
|
|
||||||
Platform: UNKNOWN
|
Platform: UNKNOWN
|
||||||
Classifier: Development Status :: 4 - Beta
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
Classifier: Programming Language :: Python :: 2
|
Classifier: Programming Language :: Python :: 2
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
Classifier: Programming Language :: Python :: 2.7
|
||||||
|
|
12
README.rst
12
README.rst
|
@ -72,12 +72,10 @@ information.
|
||||||
Support
|
Support
|
||||||
=======
|
=======
|
||||||
|
|
||||||
If you encounter bugs then please `let me know`_ . A copy of the WSDL file if
|
If you want to report a bug then please first read
|
||||||
possible would be most helpful.
|
http://docs.python-zeep.org/en/master/reporting_bugs.html
|
||||||
|
|
||||||
I'm also able to offer commercial support. As in contracting work. Please
|
I'm also able to offer commercial support. As in contracting work. Please
|
||||||
contact me at info@mvantellingen.nl for more information. If you just have a
|
contact me at info@mvantellingen.nl for more information. Note that asking
|
||||||
random question and don't intent to actually pay me for my support then please
|
questions or reporting bugs via this e-mail address will be ignored. Pleae use
|
||||||
DO NOT email me at that e-mail address but just use stackoverflow or something..
|
the appropriate channels for that (e.g. stackoverflow)
|
||||||
|
|
||||||
.. _let me know: https://github.com/mvantellingen/python-zeep/issues
|
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
from requests import Session
|
||||||
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
import zeep
|
import zeep
|
||||||
from zeep.transports import Transport
|
from zeep.transports import Transport
|
||||||
|
|
||||||
# Example using basic authentication with a webservice
|
# 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(
|
client = zeep.Client(
|
||||||
wsdl='http://nonexistent?WSDL',
|
wsdl='http://nonexistent?WSDL',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.27.0
|
current_version = 2.1.1
|
||||||
commit = true
|
commit = true
|
||||||
tag = true
|
tag = true
|
||||||
tag_name = {new_version}
|
tag_name = {new_version}
|
||||||
|
@ -19,6 +19,8 @@ max-line-length = 99
|
||||||
|
|
||||||
[bumpversion:file:docs/conf.py]
|
[bumpversion:file:docs/conf.py]
|
||||||
|
|
||||||
|
[bumpversion:file:docs/index.rst]
|
||||||
|
|
||||||
[bumpversion:file:src/zeep/__init__.py]
|
[bumpversion:file:src/zeep/__init__.py]
|
||||||
|
|
||||||
[coverage:run]
|
[coverage:run]
|
||||||
|
@ -38,5 +40,4 @@ show_missing = True
|
||||||
[egg_info]
|
[egg_info]
|
||||||
tag_build =
|
tag_build =
|
||||||
tag_date = 0
|
tag_date = 0
|
||||||
tag_svn_revision = 0
|
|
||||||
|
|
||||||
|
|
19
setup.py
19
setup.py
|
@ -1,15 +1,16 @@
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
install_requires = [
|
install_requires = [
|
||||||
'appdirs>=1.4.0',
|
'appdirs>=1.4.0',
|
||||||
'cached-property>=1.0.0',
|
'cached-property>=1.3.0',
|
||||||
'defusedxml>=0.4.1',
|
'defusedxml>=0.4.1',
|
||||||
'isodate>=0.5.4',
|
'isodate>=0.5.4',
|
||||||
'lxml>=3.0.0',
|
'lxml>=3.0.0',
|
||||||
'requests>=2.7.0',
|
'requests>=2.7.0',
|
||||||
'requests-toolbelt>=0.7.0',
|
'requests-toolbelt>=0.7.1',
|
||||||
'six>=1.9.0',
|
'six>=1.9.0',
|
||||||
'pytz',
|
'pytz',
|
||||||
]
|
]
|
||||||
|
@ -18,9 +19,7 @@ docs_require = [
|
||||||
'sphinx>=1.4.0',
|
'sphinx>=1.4.0',
|
||||||
]
|
]
|
||||||
|
|
||||||
async_require = [
|
async_require = [] # see below
|
||||||
'aiohttp>=1.0',
|
|
||||||
]
|
|
||||||
|
|
||||||
xmlsec_require = [
|
xmlsec_require = [
|
||||||
'xmlsec>=0.6.1',
|
'xmlsec>=0.6.1',
|
||||||
|
@ -41,13 +40,19 @@ tests_require = [
|
||||||
'flake8-debugger==1.4.0',
|
'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:
|
with open('README.rst') as fh:
|
||||||
long_description = re.sub(
|
long_description = re.sub(
|
||||||
'^.. start-no-pypi.*^.. end-no-pypi', '', fh.read(), flags=re.M | re.S)
|
'^.. start-no-pypi.*^.. end-no-pypi', '', fh.read(), flags=re.M | re.S)
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='zeep',
|
name='zeep',
|
||||||
version='0.27.0',
|
version='2.1.1',
|
||||||
description='A modern/fast Python SOAP client based on lxml / requests',
|
description='A modern/fast Python SOAP client based on lxml / requests',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
author="Michael van Tellingen",
|
author="Michael van Tellingen",
|
||||||
|
@ -69,7 +74,7 @@ setup(
|
||||||
|
|
||||||
license='MIT',
|
license='MIT',
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 4 - Beta',
|
'Development Status :: 5 - Production/Stable',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Programming Language :: Python :: 2',
|
'Programming Language :: Python :: 2',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 2.7',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Metadata-Version: 1.1
|
Metadata-Version: 1.1
|
||||||
Name: zeep
|
Name: zeep
|
||||||
Version: 0.27.0
|
Version: 2.1.1
|
||||||
Summary: A modern/fast Python SOAP client based on lxml / requests
|
Summary: A modern/fast Python SOAP client based on lxml / requests
|
||||||
Home-page: http://docs.python-zeep.org
|
Home-page: http://docs.python-zeep.org
|
||||||
Author: Michael van Tellingen
|
Author: Michael van Tellingen
|
||||||
|
@ -57,18 +57,16 @@ Description: ========================
|
||||||
Support
|
Support
|
||||||
=======
|
=======
|
||||||
|
|
||||||
If you encounter bugs then please `let me know`_ . A copy of the WSDL file if
|
If you want to report a bug then please first read
|
||||||
possible would be most helpful.
|
http://docs.python-zeep.org/en/master/reporting_bugs.html
|
||||||
|
|
||||||
I'm also able to offer commercial support. As in contracting work. Please
|
I'm also able to offer commercial support. As in contracting work. Please
|
||||||
contact me at info@mvantellingen.nl for more information. If you just have a
|
contact me at info@mvantellingen.nl for more information. Note that asking
|
||||||
random question and don't intent to actually pay me for my support then please
|
questions or reporting bugs via this e-mail address will be ignored. Pleae use
|
||||||
DO NOT email me at that e-mail address but just use stackoverflow or something..
|
the appropriate channels for that (e.g. stackoverflow)
|
||||||
|
|
||||||
.. _let me know: https://github.com/mvantellingen/python-zeep/issues
|
|
||||||
|
|
||||||
Platform: UNKNOWN
|
Platform: UNKNOWN
|
||||||
Classifier: Development Status :: 4 - Beta
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
Classifier: Programming Language :: Python :: 2
|
Classifier: Programming Language :: Python :: 2
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
Classifier: Programming Language :: Python :: 2.7
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
CHANGES
|
CHANGES
|
||||||
|
CONTRIBUTORS.rst
|
||||||
LICENSE
|
LICENSE
|
||||||
|
MANIFEST.in
|
||||||
README.rst
|
README.rst
|
||||||
setup.cfg
|
setup.cfg
|
||||||
setup.py
|
setup.py
|
||||||
|
@ -16,8 +18,8 @@ src/zeep/cache.py
|
||||||
src/zeep/client.py
|
src/zeep/client.py
|
||||||
src/zeep/exceptions.py
|
src/zeep/exceptions.py
|
||||||
src/zeep/helpers.py
|
src/zeep/helpers.py
|
||||||
|
src/zeep/loader.py
|
||||||
src/zeep/ns.py
|
src/zeep/ns.py
|
||||||
src/zeep/parser.py
|
|
||||||
src/zeep/plugins.py
|
src/zeep/plugins.py
|
||||||
src/zeep/transports.py
|
src/zeep/transports.py
|
||||||
src/zeep/utils.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/base.py
|
||||||
src/zeep/wsdl/messages/http.py
|
src/zeep/wsdl/messages/http.py
|
||||||
src/zeep/wsdl/messages/mime.py
|
src/zeep/wsdl/messages/mime.py
|
||||||
|
src/zeep/wsdl/messages/multiref.py
|
||||||
src/zeep/wsdl/messages/soap.py
|
src/zeep/wsdl/messages/soap.py
|
||||||
src/zeep/wsse/__init__.py
|
src/zeep/wsse/__init__.py
|
||||||
src/zeep/wsse/compose.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/collection.py
|
||||||
src/zeep/xsd/types/complex.py
|
src/zeep/xsd/types/complex.py
|
||||||
src/zeep/xsd/types/simple.py
|
src/zeep/xsd/types/simple.py
|
||||||
|
src/zeep/xsd/types/unresolved.py
|
||||||
tests/__init__.py
|
tests/__init__.py
|
||||||
tests/cert_valid.pem
|
tests/cert_valid.pem
|
||||||
tests/cert_valid_pw.pem
|
tests/cert_valid_pw.pem
|
||||||
|
@ -83,9 +87,11 @@ tests/test_cache.py
|
||||||
tests/test_client.py
|
tests/test_client.py
|
||||||
tests/test_client_factory.py
|
tests/test_client_factory.py
|
||||||
tests/test_helpers.py
|
tests/test_helpers.py
|
||||||
|
tests/test_loader.py
|
||||||
tests/test_main.py
|
tests/test_main.py
|
||||||
tests/test_pprint.py
|
tests/test_pprint.py
|
||||||
tests/test_response.py
|
tests/test_response.py
|
||||||
|
tests/test_soap_multiref.py
|
||||||
tests/test_transports.py
|
tests/test_transports.py
|
||||||
tests/test_wsa.py
|
tests/test_wsa.py
|
||||||
tests/test_wsdl.py
|
tests/test_wsdl.py
|
||||||
|
@ -101,9 +107,12 @@ tests/test_xsd.py
|
||||||
tests/test_xsd_any.py
|
tests/test_xsd_any.py
|
||||||
tests/test_xsd_attributes.py
|
tests/test_xsd_attributes.py
|
||||||
tests/test_xsd_builtins.py
|
tests/test_xsd_builtins.py
|
||||||
tests/test_xsd_choice.py
|
|
||||||
tests/test_xsd_complex_types.py
|
tests/test_xsd_complex_types.py
|
||||||
tests/test_xsd_extension.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_integration.py
|
||||||
tests/test_xsd_parse.py
|
tests/test_xsd_parse.py
|
||||||
tests/test_xsd_schemas.py
|
tests/test_xsd_schemas.py
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
appdirs>=1.4.0
|
appdirs>=1.4.0
|
||||||
cached-property>=1.0.0
|
cached-property>=1.3.0
|
||||||
defusedxml>=0.4.1
|
defusedxml>=0.4.1
|
||||||
isodate>=0.5.4
|
isodate>=0.5.4
|
||||||
lxml>=3.0.0
|
lxml>=3.0.0
|
||||||
requests>=2.7.0
|
requests>=2.7.0
|
||||||
requests-toolbelt>=0.7.0
|
requests-toolbelt>=0.7.1
|
||||||
six>=1.9.0
|
six>=1.9.0
|
||||||
pytz
|
pytz
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ isort==4.2.5
|
||||||
flake8==3.2.1
|
flake8==3.2.1
|
||||||
flake8-blind-except==0.1.1
|
flake8-blind-except==0.1.1
|
||||||
flake8-debugger==1.4.0
|
flake8-debugger==1.4.0
|
||||||
|
aioresponses>=0.1.3
|
||||||
|
|
||||||
[xmlsec]
|
[xmlsec]
|
||||||
xmlsec>=0.6.1
|
xmlsec>=0.6.1
|
||||||
|
|
|
@ -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.transports import Transport # noqa
|
||||||
from zeep.plugins import Plugin # noqa
|
from zeep.plugins import Plugin # noqa
|
||||||
from zeep.xsd.valueobjects import AnyObject # noqa
|
from zeep.xsd.valueobjects import AnyObject # noqa
|
||||||
|
|
||||||
__version__ = '0.27.0'
|
__version__ = '2.1.1'
|
||||||
|
|
|
@ -7,6 +7,7 @@ import time
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from six.moves.urllib.parse import urlparse
|
from six.moves.urllib.parse import urlparse
|
||||||
|
|
||||||
from zeep.cache import SqliteCache
|
from zeep.cache import SqliteCache
|
||||||
from zeep.client import Client
|
from zeep.client import Client
|
||||||
from zeep.transports import Transport
|
from zeep.transports import Transport
|
||||||
|
@ -27,6 +28,9 @@ def parse_arguments(args=None):
|
||||||
'--verbose', action='store_true', help='Enable verbose output')
|
'--verbose', action='store_true', help='Enable verbose output')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--profile', help="Enable profiling and save output to given file")
|
'--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)
|
return parser.parse_args(args)
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,7 +76,9 @@ def main(args):
|
||||||
|
|
||||||
transport = Transport(cache=cache, session=session)
|
transport = Transport(cache=cache, session=session)
|
||||||
st = time.time()
|
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)
|
logger.debug("Loading WSDL took %sms", (time.time() - st) * 1000)
|
||||||
|
|
||||||
if args.profile:
|
if args.profile:
|
||||||
|
|
|
@ -6,6 +6,8 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
from requests import Response
|
||||||
|
|
||||||
from zeep.transports import Transport
|
from zeep.transports import Transport
|
||||||
from zeep.utils import get_version
|
from zeep.utils import get_version
|
||||||
from zeep.wsdl.utils import etree_to_string
|
from zeep.wsdl.utils import etree_to_string
|
||||||
|
@ -27,9 +29,14 @@ class AsyncTransport(Transport):
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
self.session = session or aiohttp.ClientSession(loop=self.loop)
|
self.session = session or aiohttp.ClientSession(loop=self.loop)
|
||||||
|
self._close_session = session is None
|
||||||
self.session._default_headers['User-Agent'] = (
|
self.session._default_headers['User-Agent'] = (
|
||||||
'Zeep/%s (www.python-zeep.org)' % (get_version()))
|
'Zeep/%s (www.python-zeep.org)' % (get_version()))
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if self._close_session:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
def _load_remote_data(self, url):
|
def _load_remote_data(self, url):
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
|
@ -56,20 +63,21 @@ class AsyncTransport(Transport):
|
||||||
async def post_xml(self, address, envelope, headers):
|
async def post_xml(self, address, envelope, headers):
|
||||||
message = etree_to_string(envelope)
|
message = etree_to_string(envelope)
|
||||||
response = await self.post(address, message, headers)
|
response = await self.post(address, message, headers)
|
||||||
|
return await self.new_response(response)
|
||||||
from pretend import stub
|
|
||||||
return stub(
|
|
||||||
content=await response.read(),
|
|
||||||
status_code=response.status,
|
|
||||||
headers=response.headers)
|
|
||||||
|
|
||||||
async def get(self, address, params, headers):
|
async def get(self, address, params, headers):
|
||||||
with aiohttp.Timeout(self.operation_timeout):
|
with aiohttp.Timeout(self.operation_timeout):
|
||||||
response = await self.session.get(
|
response = await self.session.get(
|
||||||
address, params=params, headers=headers)
|
address, params=params, headers=headers)
|
||||||
|
|
||||||
from pretend import stub
|
return await self.new_response(response)
|
||||||
return await stub(
|
|
||||||
content=await response.read(),
|
async def new_response(self, response):
|
||||||
status_code=response.status,
|
"""Convert an aiohttp.Response object to a requests.Response object"""
|
||||||
headers=response.headers)
|
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
|
||||||
|
|
|
@ -4,7 +4,7 @@ from contextlib import contextmanager
|
||||||
|
|
||||||
from zeep.transports import Transport
|
from zeep.transports import Transport
|
||||||
from zeep.wsdl import Document
|
from zeep.wsdl import Document
|
||||||
|
from zeep.xsd.const import NotSet
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -15,6 +15,12 @@ class OperationProxy(object):
|
||||||
self._op_name = operation_name
|
self._op_name = operation_name
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
|
"""Call the operation with the given args and kwargs.
|
||||||
|
|
||||||
|
:rtype: zeep.xsd.CompoundValue
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
if self._proxy._client._default_soapheaders:
|
if self._proxy._client._default_soapheaders:
|
||||||
op_soapheaders = kwargs.get('_soapheaders')
|
op_soapheaders = kwargs.get('_soapheaders')
|
||||||
if op_soapheaders:
|
if op_soapheaders:
|
||||||
|
@ -42,9 +48,19 @@ class ServiceProxy(object):
|
||||||
self._binding = binding
|
self._binding = binding
|
||||||
|
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
|
"""Return the OperationProxy for the given key.
|
||||||
|
|
||||||
|
:rtype: OperationProxy()
|
||||||
|
|
||||||
|
"""
|
||||||
return self[key]
|
return self[key]
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
"""Return the OperationProxy for the given key.
|
||||||
|
|
||||||
|
:rtype: OperationProxy()
|
||||||
|
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
self._binding.get(key)
|
self._binding.get(key)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -62,9 +78,19 @@ class Factory(object):
|
||||||
self._ns = types.get_ns_prefix(namespace)
|
self._ns = types.get_ns_prefix(namespace)
|
||||||
|
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
|
"""Return the complexType or simpleType for the given localname.
|
||||||
|
|
||||||
|
:rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
|
||||||
|
|
||||||
|
"""
|
||||||
return self[key]
|
return self[key]
|
||||||
|
|
||||||
def __getitem__(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))
|
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
|
first port defined in the service element in the WSDL
|
||||||
document.
|
document.
|
||||||
:param plugins: a list of Plugin instances
|
: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,
|
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:
|
if not wsdl:
|
||||||
raise ValueError("No URL given for the wsdl")
|
raise ValueError("No URL given for the wsdl")
|
||||||
|
|
||||||
self.transport = transport or Transport()
|
self.transport = transport if transport is not None else Transport()
|
||||||
self.wsdl = Document(wsdl, self.transport)
|
self.wsdl = Document(wsdl, self.transport, strict=strict)
|
||||||
self.wsse = wsse
|
self.wsse = wsse
|
||||||
self.plugins = plugins if plugins is not None else []
|
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 = None
|
||||||
self._default_service_name = service_name
|
self._default_service_name = service_name
|
||||||
|
@ -102,7 +135,11 @@ class Client(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def service(self):
|
def service(self):
|
||||||
"""The default ServiceProxy instance"""
|
"""The default ServiceProxy instance
|
||||||
|
|
||||||
|
:rtype: ServiceProxy
|
||||||
|
|
||||||
|
"""
|
||||||
if self._default_service:
|
if self._default_service:
|
||||||
return self._default_service
|
return self._default_service
|
||||||
|
|
||||||
|
@ -116,7 +153,7 @@ class Client(object):
|
||||||
return self._default_service
|
return self._default_service
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def options(self, timeout):
|
def options(self, timeout=NotSet, raw_response=NotSet):
|
||||||
"""Context manager to temporarily overrule various options.
|
"""Context manager to temporarily overrule various options.
|
||||||
|
|
||||||
:param timeout: Set the timeout for POST/GET operations (not used for
|
: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):
|
# Store current options
|
||||||
yield
|
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):
|
def bind(self, service_name=None, port_name=None):
|
||||||
"""Create a new ServiceProxy for the given service_name and port_name.
|
"""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())))
|
"are: %s" % (', '.join(self.wsdl.bindings.keys())))
|
||||||
return ServiceProxy(self, binding, address=address)
|
return ServiceProxy(self, binding, address=address)
|
||||||
|
|
||||||
def create_message(self, operation, service_name=None, port_name=None,
|
def create_message(self, service, operation_name, *args, **kwargs):
|
||||||
args=None, kwargs=None):
|
"""Create the payload for the given operation.
|
||||||
"""Create the payload for the given operation."""
|
|
||||||
service = self._get_service(service_name)
|
|
||||||
port = self._get_port(service, port_name)
|
|
||||||
|
|
||||||
args = args or tuple()
|
:rtype: lxml.etree._Element
|
||||||
kwargs = kwargs or {}
|
|
||||||
envelope, http_headers = port.binding._create(operation, args, kwargs)
|
"""
|
||||||
|
envelope, http_headers = service._binding._create(
|
||||||
|
operation_name, args, kwargs, client=self)
|
||||||
return envelope
|
return envelope
|
||||||
|
|
||||||
def type_factory(self, namespace):
|
def type_factory(self, namespace):
|
||||||
|
@ -182,15 +232,25 @@ class Client(object):
|
||||||
factory = client.type_factory('ns0')
|
factory = client.type_factory('ns0')
|
||||||
user = factory.User(name='John')
|
user = factory.User(name='John')
|
||||||
|
|
||||||
|
:rtype: Factory
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return Factory(self.wsdl.types, 'type', namespace)
|
return Factory(self.wsdl.types, 'type', namespace)
|
||||||
|
|
||||||
def get_type(self, name):
|
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)
|
return self.wsdl.types.get_type(name)
|
||||||
|
|
||||||
def get_element(self, 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)
|
return self.wsdl.types.get_element(name)
|
||||||
|
|
||||||
def set_ns_prefix(self, prefix, namespace):
|
def set_ns_prefix(self, prefix, namespace):
|
||||||
|
@ -227,3 +287,20 @@ class Client(object):
|
||||||
else:
|
else:
|
||||||
service = next(iter(self.wsdl.services.values()), None)
|
service = next(iter(self.wsdl.services.values()), None)
|
||||||
return service
|
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)
|
||||||
|
|
|
@ -39,7 +39,11 @@ class TransportError(Error):
|
||||||
|
|
||||||
|
|
||||||
class LookupError(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):
|
class NamespaceError(Error):
|
||||||
|
@ -74,3 +78,11 @@ class ValidationError(Error):
|
||||||
|
|
||||||
class SignatureVerificationFailed(Error):
|
class SignatureVerificationFailed(Error):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IncompleteMessage(Error):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IncompleteOperation(Error):
|
||||||
|
pass
|
||||||
|
|
|
@ -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)
|
|
@ -4,6 +4,7 @@ SOAP_12 = 'http://schemas.xmlsoap.org/wsdl/soap12/'
|
||||||
SOAP_ENV_11 = 'http://schemas.xmlsoap.org/soap/envelope/'
|
SOAP_ENV_11 = 'http://schemas.xmlsoap.org/soap/envelope/'
|
||||||
SOAP_ENV_12 = 'http://www.w3.org/2003/05/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'
|
XSD = 'http://www.w3.org/2001/XMLSchema'
|
||||||
|
|
||||||
WSDL = 'http://schemas.xmlsoap.org/wsdl/'
|
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#'
|
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'
|
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'
|
WSU = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
|
||||||
|
|
||||||
|
NAMESPACE_TO_PREFIX = {
|
||||||
|
XSD: 'xsd',
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
|
|
@ -3,9 +3,9 @@ import os
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlparse
|
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
|
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 timeout: The timeout for loading wsdl and xsd documents.
|
||||||
:param operation_timeout: The timeout for operations (POST/GET). By
|
:param operation_timeout: The timeout for operations (POST/GET). By
|
||||||
default this is None (no timeout).
|
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
|
supports_async = False
|
||||||
|
@ -68,7 +68,10 @@ class Transport(object):
|
||||||
timeout=self.operation_timeout)
|
timeout=self.operation_timeout)
|
||||||
|
|
||||||
if self.logger.isEnabledFor(logging.DEBUG):
|
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
|
log_message = response.content
|
||||||
else:
|
else:
|
||||||
log_message = response.content
|
log_message = response.content
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
|
import cgi
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
from zeep.exceptions import XMLParseError
|
||||||
|
from zeep.ns import XSD
|
||||||
|
|
||||||
|
|
||||||
def qname_attr(node, attr_name, target_namespace=None):
|
def qname_attr(node, attr_name, target_namespace=None):
|
||||||
value = node.get(attr_name)
|
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)
|
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"""
|
"""Convert the given value to a QName"""
|
||||||
if ':' in value:
|
if ':' in value:
|
||||||
prefix, local = value.split(':')
|
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)
|
return etree.QName(namespace, local)
|
||||||
|
|
||||||
if target_namespace:
|
if target_namespace:
|
||||||
|
@ -61,3 +79,9 @@ def get_base_class(objects):
|
||||||
def detect_soap_env(envelope):
|
def detect_soap_env(envelope):
|
||||||
root_tag = etree.QName(envelope)
|
root_tag = etree.QName(envelope)
|
||||||
return root_tag.namespace
|
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
|
||||||
|
|
|
@ -37,7 +37,5 @@ class WsAddressingPlugin(Plugin):
|
||||||
keep_ns_prefixes=header.nsmap,
|
keep_ns_prefixes=header.nsmap,
|
||||||
top_nsmap=self.nsmap)
|
top_nsmap=self.nsmap)
|
||||||
else:
|
else:
|
||||||
etree.cleanup_namespaces(
|
etree.cleanup_namespaces(header)
|
||||||
header,
|
|
||||||
keep_ns_prefixes=header.nsmap)
|
|
||||||
return envelope, http_headers
|
return envelope, http_headers
|
||||||
|
|
|
@ -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
|
from zeep.wsdl.wsdl import Document # noqa
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
See https://www.w3.org/TR/SOAP-attachments
|
See https://www.w3.org/TR/SOAP-attachments
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
from cached_property import cached_property
|
from cached_property import cached_property
|
||||||
from requests.structures import CaseInsensitiveDict
|
from requests.structures import CaseInsensitiveDict
|
||||||
|
@ -27,9 +27,21 @@ class MessagePack(object):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def attachments(self):
|
def attachments(self):
|
||||||
|
"""Return a list of attachments.
|
||||||
|
|
||||||
|
:rtype: list of Attachment
|
||||||
|
|
||||||
|
"""
|
||||||
return [Attachment(part) for part in self._parts]
|
return [Attachment(part) for part in self._parts]
|
||||||
|
|
||||||
def get_by_content_id(self, content_id):
|
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:
|
for attachment in self.attachments:
|
||||||
if attachment.content_id == content_id:
|
if attachment.content_id == content_id:
|
||||||
return attachment
|
return attachment
|
||||||
|
@ -37,9 +49,9 @@ class MessagePack(object):
|
||||||
|
|
||||||
class Attachment(object):
|
class Attachment(object):
|
||||||
def __init__(self, part):
|
def __init__(self, part):
|
||||||
|
encoding = part.encoding or 'utf-8'
|
||||||
self.headers = CaseInsensitiveDict({
|
self.headers = CaseInsensitiveDict({
|
||||||
k.decode(part.encoding): v.decode(part.encoding)
|
k.decode(encoding): v.decode(encoding)
|
||||||
for k, v in part.headers.items()
|
for k, v in part.headers.items()
|
||||||
})
|
})
|
||||||
self.content_type = self.headers.get('Content-Type', None)
|
self.content_type = self.headers.get('Content-Type', None)
|
||||||
|
@ -52,6 +64,11 @@ class Attachment(object):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def content(self):
|
def content(self):
|
||||||
|
"""Return the content of the attachment
|
||||||
|
|
||||||
|
:rtype: bytes or str
|
||||||
|
|
||||||
|
"""
|
||||||
encoding = self.headers.get('Content-Transfer-Encoding', None)
|
encoding = self.headers.get('Content-Transfer-Encoding', None)
|
||||||
content = self._part.content
|
content = self._part.content
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ from requests_toolbelt.multipart.decoder import MultipartDecoder
|
||||||
|
|
||||||
from zeep import ns, plugins, wsa
|
from zeep import ns, plugins, wsa
|
||||||
from zeep.exceptions import Fault, TransportError, XMLSyntaxError
|
from zeep.exceptions import Fault, TransportError, XMLSyntaxError
|
||||||
from zeep.parser import parse_xml
|
from zeep.loader import parse_xml
|
||||||
from zeep.utils import as_qname, qname_attr
|
from zeep.utils import as_qname, get_media_type, qname_attr
|
||||||
from zeep.wsdl.attachments import MessagePack
|
from zeep.wsdl.attachments import MessagePack
|
||||||
from zeep.wsdl.definitions import Binding, Operation
|
from zeep.wsdl.definitions import Binding, Operation
|
||||||
from zeep.wsdl.messages import DocumentMessage, RpcMessage
|
from zeep.wsdl.messages import DocumentMessage, RpcMessage
|
||||||
|
@ -97,9 +97,9 @@ class SoapBinding(Binding):
|
||||||
:type options: dict
|
:type options: dict
|
||||||
:param operation: The operation object from which this is a reply
|
:param operation: The operation object from which this is a reply
|
||||||
:type operation: zeep.wsdl.definitions.Operation
|
: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
|
:type args: tuple
|
||||||
:param kwargs: The **kwargs to pass to the operation
|
:param kwargs: The kwargs to pass to the operation
|
||||||
:type kwargs: dict
|
:type kwargs: dict
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -112,6 +112,11 @@ class SoapBinding(Binding):
|
||||||
options['address'], envelope, http_headers)
|
options['address'], envelope, http_headers)
|
||||||
|
|
||||||
operation_obj = self.get(operation)
|
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)
|
return self.process_reply(client, operation_obj, response)
|
||||||
|
|
||||||
def process_reply(self, client, operation, response):
|
def process_reply(self, client, operation, response):
|
||||||
|
@ -131,20 +136,27 @@ class SoapBinding(Binding):
|
||||||
% response.status_code)
|
% response.status_code)
|
||||||
|
|
||||||
content_type = response.headers.get('Content-Type', 'text/xml')
|
content_type = response.headers.get('Content-Type', 'text/xml')
|
||||||
if 'multipart/related' in content_type:
|
media_type = get_media_type(content_type)
|
||||||
decoder = MultipartDecoder(response.content, content_type, 'utf-8')
|
message_pack = None
|
||||||
|
|
||||||
|
if media_type == 'multipart/related':
|
||||||
|
decoder = MultipartDecoder(
|
||||||
|
response.content, content_type, response.encoding or 'utf-8')
|
||||||
|
|
||||||
content = decoder.parts[0].content
|
content = decoder.parts[0].content
|
||||||
if len(decoder.parts) > 1:
|
if len(decoder.parts) > 1:
|
||||||
message_pack = MessagePack(parts=decoder.parts[1:])
|
message_pack = MessagePack(parts=decoder.parts[1:])
|
||||||
else:
|
else:
|
||||||
content = response.content
|
content = response.content
|
||||||
message_pack = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
doc = parse_xml(content)
|
doc = parse_xml(
|
||||||
|
content, self.transport,
|
||||||
|
strict=client.wsdl.strict,
|
||||||
|
xml_huge_tree=client.xml_huge_tree)
|
||||||
except XMLSyntaxError:
|
except XMLSyntaxError:
|
||||||
raise TransportError(
|
raise TransportError(
|
||||||
u'Server returned HTTP status %d (%s)'
|
'Server returned HTTP status %d (%s)'
|
||||||
% (response.status_code, response.content))
|
% (response.status_code, response.content))
|
||||||
|
|
||||||
if client.wsse:
|
if client.wsse:
|
||||||
|
@ -187,6 +199,9 @@ class SoapBinding(Binding):
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, definitions, xmlelement):
|
def parse(cls, definitions, xmlelement):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<wsdl:binding name="nmtoken" type="qname"> *
|
<wsdl:binding name="nmtoken" type="qname"> *
|
||||||
<-- extensibility element (1) --> *
|
<-- extensibility element (1) --> *
|
||||||
<wsdl:operation name="nmtoken"> *
|
<wsdl:operation name="nmtoken"> *
|
||||||
|
@ -210,7 +225,13 @@ class SoapBinding(Binding):
|
||||||
# default style attribute for the operations.
|
# default style attribute for the operations.
|
||||||
soap_node = xmlelement.find('soap:binding', namespaces=cls.nsmap)
|
soap_node = xmlelement.find('soap:binding', namespaces=cls.nsmap)
|
||||||
transport = soap_node.get('transport')
|
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(
|
raise NotImplementedError(
|
||||||
"The binding transport %s is not supported (only soap/http)" % (
|
"The binding transport %s is not supported (only soap/http)" % (
|
||||||
transport))
|
transport))
|
||||||
|
@ -328,12 +349,15 @@ class SoapOperation(Operation):
|
||||||
"{%s}Envelope root element. The root element found is %s "
|
"{%s}Envelope root element. The root element found is %s "
|
||||||
) % (envelope_qname.namespace, envelope.tag))
|
) % (envelope_qname.namespace, envelope.tag))
|
||||||
|
|
||||||
return self.output.deserialize(envelope)
|
if self.output:
|
||||||
|
return self.output.deserialize(envelope)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, definitions, xmlelement, binding, nsmap):
|
def parse(cls, definitions, xmlelement, binding, nsmap):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<wsdl:operation name="nmtoken"> *
|
<wsdl:operation name="nmtoken"> *
|
||||||
<soap:operation soapAction="uri"? style="rpc|document"?>?
|
<soap:operation soapAction="uri"? style="rpc|document"?>?
|
||||||
<wsdl:input name="nmtoken"? > ?
|
<wsdl:input name="nmtoken"? > ?
|
||||||
|
|
|
@ -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 collections import OrderedDict, namedtuple
|
||||||
|
|
||||||
from six import python_2_unicode_compatible
|
from six import python_2_unicode_compatible
|
||||||
|
|
||||||
|
from zeep.exceptions import IncompleteOperation
|
||||||
|
|
||||||
MessagePart = namedtuple('MessagePart', ['element', 'type'])
|
MessagePart = namedtuple('MessagePart', ['element', 'type'])
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,8 +33,8 @@ class AbstractMessage(object):
|
||||||
extensible. WSDL defines several such message-typing attributes for use
|
extensible. WSDL defines several such message-typing attributes for use
|
||||||
with XSD:
|
with XSD:
|
||||||
|
|
||||||
element: Refers to an XSD element using a QName.
|
- element: Refers to an XSD element using a QName.
|
||||||
type: Refers to an XSD simpleType or complexType using a QName.
|
- type: Refers to an XSD simpleType or complexType using a QName.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
|
@ -72,6 +92,8 @@ class PortType(object):
|
||||||
class Binding(object):
|
class Binding(object):
|
||||||
"""Base class for the various bindings (SoapBinding / HttpBinding)
|
"""Base class for the various bindings (SoapBinding / HttpBinding)
|
||||||
|
|
||||||
|
.. raw:: ascii
|
||||||
|
|
||||||
Binding
|
Binding
|
||||||
|
|
|
|
||||||
+-> Operation
|
+-> Operation
|
||||||
|
@ -100,8 +122,13 @@ class Binding(object):
|
||||||
|
|
||||||
def resolve(self, definitions):
|
def resolve(self, definitions):
|
||||||
self.port_type = definitions.get('port_types', self.port_name.text)
|
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):
|
def _operation_add(self, operation):
|
||||||
# XXX: operation name is not unique
|
# XXX: operation name is not unique
|
||||||
|
@ -146,7 +173,12 @@ class Operation(object):
|
||||||
self.faults = {}
|
self.faults = {}
|
||||||
|
|
||||||
def resolve(self, definitions):
|
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):
|
def __repr__(self):
|
||||||
return '<%s(name=%r, style=%r)>' % (
|
return '<%s(name=%r, style=%r)>' % (
|
||||||
|
@ -170,6 +202,9 @@ class Operation(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, wsdl, xmlelement, binding):
|
def parse(cls, wsdl, xmlelement, binding):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<wsdl:operation name="nmtoken"> *
|
<wsdl:operation name="nmtoken"> *
|
||||||
<-- extensibility element (2) --> *
|
<-- extensibility element (2) --> *
|
||||||
<wsdl:input name="nmtoken"? > ?
|
<wsdl:input name="nmtoken"? > ?
|
||||||
|
@ -182,12 +217,17 @@ class Operation(object):
|
||||||
<-- extensibility element (5) --> *
|
<-- extensibility element (5) --> *
|
||||||
</wsdl:fault>
|
</wsdl:fault>
|
||||||
</wsdl:operation>
|
</wsdl:operation>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Port(object):
|
class Port(object):
|
||||||
|
"""Specifies an address for a binding, thus defining a single communication
|
||||||
|
endpoint.
|
||||||
|
|
||||||
|
"""
|
||||||
def __init__(self, name, binding_name, xmlelement):
|
def __init__(self, name, binding_name, xmlelement):
|
||||||
self.name = name
|
self.name = name
|
||||||
self._resolve_context = {
|
self._resolve_context = {
|
||||||
|
@ -231,7 +271,9 @@ class Port(object):
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Service(object):
|
class Service(object):
|
||||||
|
"""Used to aggregate a set of related ports.
|
||||||
|
|
||||||
|
"""
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.ports = OrderedDict()
|
self.ports = OrderedDict()
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
|
@ -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 .http import * # noqa
|
||||||
from .mime import * # noqa
|
from .mime import * # noqa
|
||||||
from .soap import * # noqa
|
from .soap import * # noqa
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
"""
|
||||||
|
zeep.wsdl.messages.base
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
"""
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from zeep import xsd
|
from zeep import xsd
|
||||||
|
@ -31,15 +36,17 @@ class ConcreteMessage(object):
|
||||||
if isinstance(self.body.type, xsd.ComplexType):
|
if isinstance(self.body.type, xsd.ComplexType):
|
||||||
try:
|
try:
|
||||||
if len(self.body.type.elements) == 1:
|
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:
|
except AttributeError:
|
||||||
return None
|
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):
|
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)
|
return ', '.join(part for part in parts if part)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
"""
|
||||||
|
zeep.wsdl.messages.http
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
"""
|
||||||
from zeep import xsd
|
from zeep import xsd
|
||||||
from zeep.wsdl.messages.base import ConcreteMessage, SerializedMessage
|
from zeep.wsdl.messages.base import ConcreteMessage, SerializedMessage
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
"""
|
||||||
|
zeep.wsdl.messages.mime
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
"""
|
||||||
import six
|
import six
|
||||||
from defusedxml.lxml import fromstring
|
from defusedxml.lxml import fromstring
|
||||||
from lxml import etree
|
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
|
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.
|
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):
|
def __init__(self, wsdl, name, operation, content_type, part_name):
|
||||||
super(MimeContent, self).__init__(wsdl, name, operation, 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
|
only a single part. The part references a concrete schema using the element
|
||||||
attribute for simple parts or type attribute for composite parts
|
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):
|
def serialize(self, *args, **kwargs):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
@ -170,5 +191,13 @@ class MimeMultipart(MimeMessage):
|
||||||
the part. If more than one MIME element appears inside a mime:part, they
|
the part. If more than one MIME element appears inside a mime:part, they
|
||||||
are alternatives.
|
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
|
pass
|
||||||
|
|
|
@ -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
|
|
@ -1,12 +1,19 @@
|
||||||
|
"""
|
||||||
|
zeep.wsdl.messages.soap
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
"""
|
||||||
import copy
|
import copy
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from lxml.builder import ElementMaker
|
from lxml.builder import ElementMaker
|
||||||
|
|
||||||
|
from zeep import ns
|
||||||
from zeep import exceptions, xsd
|
from zeep import exceptions, xsd
|
||||||
from zeep.utils import as_qname
|
from zeep.utils import as_qname
|
||||||
from zeep.wsdl.messages.base import ConcreteMessage, SerializedMessage
|
from zeep.wsdl.messages.base import ConcreteMessage, SerializedMessage
|
||||||
|
from zeep.wsdl.messages.multiref import process_multiref
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'DocumentMessage',
|
'DocumentMessage',
|
||||||
|
@ -15,8 +22,19 @@ __all__ = [
|
||||||
|
|
||||||
|
|
||||||
class SoapMessage(ConcreteMessage):
|
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):
|
def __init__(self, wsdl, name, operation, type, nsmap):
|
||||||
super(SoapMessage, self).__init__(wsdl, name, operation)
|
super(SoapMessage, self).__init__(wsdl, name, operation)
|
||||||
self.nsmap = nsmap
|
self.nsmap = nsmap
|
||||||
|
@ -71,6 +89,7 @@ class SoapMessage(ConcreteMessage):
|
||||||
if not self.envelope:
|
if not self.envelope:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
body = envelope.find('soap-env:Body', namespaces=self.nsmap)
|
body = envelope.find('soap-env:Body', namespaces=self.nsmap)
|
||||||
body_result = self._deserialize_body(body)
|
body_result = self._deserialize_body(body)
|
||||||
|
|
||||||
|
@ -96,7 +115,8 @@ class SoapMessage(ConcreteMessage):
|
||||||
result = next(iter(result.__values__.values()))
|
result = next(iter(result.__values__.values()))
|
||||||
if isinstance(result, xsd.CompoundValue):
|
if isinstance(result, xsd.CompoundValue):
|
||||||
children = result._xsd_type.elements
|
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]
|
item_name, item_element = children[0]
|
||||||
retval = getattr(result, item_name)
|
retval = getattr(result, item_name)
|
||||||
return retval
|
return retval
|
||||||
|
@ -110,14 +130,16 @@ class SoapMessage(ConcreteMessage):
|
||||||
if isinstance(self.envelope.type, xsd.ComplexType):
|
if isinstance(self.envelope.type, xsd.ComplexType):
|
||||||
try:
|
try:
|
||||||
if len(self.envelope.type.elements) == 1:
|
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:
|
except AttributeError:
|
||||||
return None
|
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:
|
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)
|
return ', '.join(part for part in parts if part)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -156,6 +178,8 @@ class SoapMessage(ConcreteMessage):
|
||||||
body_data = None
|
body_data = None
|
||||||
header_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)
|
body = xmlelement.find('soap:body', namespaces=operation.binding.nsmap)
|
||||||
if body is not None:
|
if body is not None:
|
||||||
body_data = cls._parse_body(body)
|
body_data = cls._parse_body(body)
|
||||||
|
@ -270,15 +294,16 @@ class SoapMessage(ConcreteMessage):
|
||||||
elements from the body and the headers.
|
elements from the body and the headers.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
all_elements = xsd.Sequence([
|
all_elements = xsd.Sequence([])
|
||||||
xsd.Element('body', self.body.type),
|
|
||||||
])
|
|
||||||
|
|
||||||
if self.header.type._element:
|
if self.header.type._element:
|
||||||
all_elements.append(
|
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):
|
def _serialize_header(self, headers_value, nsmap):
|
||||||
if not headers_value:
|
if not headers_value:
|
||||||
|
@ -327,9 +352,9 @@ class SoapMessage(ConcreteMessage):
|
||||||
def _resolve_header(self, info, definitions, parts):
|
def _resolve_header(self, info, definitions, parts):
|
||||||
name = etree.QName(self.nsmap['soap-env'], 'Header')
|
name = etree.QName(self.nsmap['soap-env'], 'Header')
|
||||||
|
|
||||||
sequence = xsd.Sequence()
|
container = xsd.All(consume_other=True)
|
||||||
if not info:
|
if not info:
|
||||||
return xsd.Element(name, xsd.ComplexType(sequence))
|
return xsd.Element(name, xsd.ComplexType(container))
|
||||||
|
|
||||||
for item in info:
|
for item in info:
|
||||||
message_name = item['message'].text
|
message_name = item['message'].text
|
||||||
|
@ -345,14 +370,28 @@ class SoapMessage(ConcreteMessage):
|
||||||
element.attr_name = part_name
|
element.attr_name = part_name
|
||||||
else:
|
else:
|
||||||
element = xsd.Element(part_name, part.type)
|
element = xsd.Element(part_name, part.type)
|
||||||
sequence.append(element)
|
container.append(element)
|
||||||
return xsd.Element(name, xsd.ComplexType(sequence))
|
return xsd.Element(name, xsd.ComplexType(container))
|
||||||
|
|
||||||
|
|
||||||
class DocumentMessage(SoapMessage):
|
class DocumentMessage(SoapMessage):
|
||||||
"""In the document message there are no additional wrappers, and the
|
"""In the document message there are no additional wrappers, and the
|
||||||
message parts appear directly under the SOAP Body element.
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -407,6 +446,20 @@ class RpcMessage(SoapMessage):
|
||||||
identically to the corresponding parameter of the call. Parts are arranged
|
identically to the corresponding parameter of the call. Parts are arranged
|
||||||
in the same order as the parameters of the call.
|
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):
|
def _resolve_body(self, info, definitions, parts):
|
||||||
|
@ -444,6 +497,8 @@ class RpcMessage(SoapMessage):
|
||||||
element.
|
element.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
process_multiref(body_element)
|
||||||
|
|
||||||
response_element = body_element.getchildren()[0]
|
response_element = body_element.getchildren()[0]
|
||||||
if self.body:
|
if self.body:
|
||||||
result = self.body.parse(response_element, self.wsdl.types)
|
result = self.body.parse(response_element, self.wsdl.types)
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
|
"""
|
||||||
|
zeep.wsdl.parse
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
"""
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
from zeep.exceptions import IncompleteMessage, LookupError, NamespaceError
|
||||||
from zeep.utils import qname_attr
|
from zeep.utils import qname_attr
|
||||||
from zeep.wsdl import definitions
|
from zeep.wsdl import definitions
|
||||||
|
|
||||||
|
@ -12,11 +18,20 @@ NSMAP = {
|
||||||
def parse_abstract_message(wsdl, xmlelement):
|
def parse_abstract_message(wsdl, xmlelement):
|
||||||
"""Create an AbstractMessage object from a xml element.
|
"""Create an AbstractMessage object from a xml element.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<definitions .... >
|
<definitions .... >
|
||||||
<message name="nmtoken"> *
|
<message name="nmtoken"> *
|
||||||
<part name="nmtoken" element="qname"? type="qname"?/> *
|
<part name="nmtoken" element="qname"? type="qname"?/> *
|
||||||
</message>
|
</message>
|
||||||
</definitions>
|
</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
|
tns = wsdl.target_namespace
|
||||||
parts = []
|
parts = []
|
||||||
|
@ -26,10 +41,17 @@ def parse_abstract_message(wsdl, xmlelement):
|
||||||
part_element = qname_attr(part, 'element', tns)
|
part_element = qname_attr(part, 'element', tns)
|
||||||
part_type = qname_attr(part, 'type', tns)
|
part_type = qname_attr(part, 'type', tns)
|
||||||
|
|
||||||
if part_element is not None:
|
try:
|
||||||
part_element = wsdl.types.get_element(part_element)
|
if part_element is not None:
|
||||||
if part_type is not None:
|
part_element = wsdl.types.get_element(part_element)
|
||||||
part_type = wsdl.types.get_type(part_type)
|
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)
|
part = definitions.MessagePart(part_element, part_type)
|
||||||
parts.append((part_name, part))
|
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
|
This is called from the parse_port_type function since the abstract
|
||||||
operations are part of the port type element.
|
operations are part of the port type element.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<wsdl:operation name="nmtoken">*
|
<wsdl:operation name="nmtoken">*
|
||||||
<wsdl:documentation .... /> ?
|
<wsdl:documentation .... /> ?
|
||||||
<wsdl:input name="nmtoken"? message="qname">?
|
<wsdl:input name="nmtoken"? message="qname">?
|
||||||
|
@ -61,6 +85,12 @@ def parse_abstract_operation(wsdl, xmlelement):
|
||||||
</wsdl:fault>
|
</wsdl:fault>
|
||||||
</wsdl:operation>
|
</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')
|
name = xmlelement.get('name')
|
||||||
kwargs = {
|
kwargs = {
|
||||||
|
@ -75,7 +105,11 @@ def parse_abstract_operation(wsdl, xmlelement):
|
||||||
param_msg = qname_attr(
|
param_msg = qname_attr(
|
||||||
msg_node, 'message', wsdl.target_namespace)
|
msg_node, 'message', wsdl.target_namespace)
|
||||||
param_name = msg_node.get('name')
|
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':
|
if tag_name == 'input':
|
||||||
kwargs['input_message'] = param_value
|
kwargs['input_message'] = param_value
|
||||||
|
@ -95,18 +129,27 @@ def parse_abstract_operation(wsdl, xmlelement):
|
||||||
def parse_port_type(wsdl, xmlelement):
|
def parse_port_type(wsdl, xmlelement):
|
||||||
"""Create a PortType object from a xml element.
|
"""Create a PortType object from a xml element.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<wsdl:definitions .... >
|
<wsdl:definitions .... >
|
||||||
<wsdl:portType name="nmtoken">
|
<wsdl:portType name="nmtoken">
|
||||||
<wsdl:operation name="nmtoken" .... /> *
|
<wsdl:operation name="nmtoken" .... /> *
|
||||||
</wsdl:portType>
|
</wsdl:portType>
|
||||||
</wsdl:definitions>
|
</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)
|
name = qname_attr(xmlelement, 'name', wsdl.target_namespace)
|
||||||
operations = {}
|
operations = {}
|
||||||
for elm in xmlelement.findall('wsdl:operation', namespaces=NSMAP):
|
for elm in xmlelement.findall('wsdl:operation', namespaces=NSMAP):
|
||||||
operation = parse_abstract_operation(wsdl, elm)
|
operation = parse_abstract_operation(wsdl, elm)
|
||||||
operations[operation.name] = operation
|
if operation:
|
||||||
|
operations[operation.name] = operation
|
||||||
return definitions.PortType(name, operations)
|
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
|
This is called via the parse_service function since ports are part of the
|
||||||
service xml elements.
|
service xml elements.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<wsdl:port name="nmtoken" binding="qname"> *
|
<wsdl:port name="nmtoken" binding="qname"> *
|
||||||
<wsdl:documentation .... /> ?
|
<wsdl:documentation .... /> ?
|
||||||
<-- extensibility element -->
|
<-- extensibility element -->
|
||||||
</wsdl:port>
|
</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')
|
name = xmlelement.get('name')
|
||||||
binding_name = qname_attr(xmlelement, 'binding', wsdl.target_namespace)
|
binding_name = qname_attr(xmlelement, 'binding', wsdl.target_namespace)
|
||||||
|
@ -130,7 +181,7 @@ def parse_port(wsdl, xmlelement):
|
||||||
def parse_service(wsdl, xmlelement):
|
def parse_service(wsdl, xmlelement):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Syntax::
|
Definition::
|
||||||
|
|
||||||
<wsdl:service name="nmtoken"> *
|
<wsdl:service name="nmtoken"> *
|
||||||
<wsdl:documentation .... />?
|
<wsdl:documentation .... />?
|
||||||
|
@ -150,6 +201,12 @@ def parse_service(wsdl, xmlelement):
|
||||||
</port>
|
</port>
|
||||||
</service>
|
</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')
|
name = xmlelement.get('name')
|
||||||
ports = []
|
ports = []
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
"""
|
||||||
|
zeep.wsdl.utils
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
"""
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from six.moves.urllib.parse import urlparse, urlunparse
|
from six.moves.urllib.parse import urlparse, urlunparse
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
|
"""
|
||||||
|
zeep.wsdl.wsdl
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import operator
|
import operator
|
||||||
import os
|
import os
|
||||||
|
import warnings
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import six
|
import six
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from zeep.parser import (
|
from zeep.exceptions import IncompleteMessage
|
||||||
absolute_location, is_relative_path, load_external, parse_xml)
|
from zeep.loader import absolute_location, is_relative_path, load_external
|
||||||
from zeep.utils import findall_multiple_ns
|
from zeep.utils import findall_multiple_ns
|
||||||
from zeep.wsdl import parse
|
from zeep.wsdl import parse
|
||||||
from zeep.xsd import Schema
|
from zeep.xsd import Schema
|
||||||
|
@ -34,18 +40,23 @@ class Document(object):
|
||||||
resolves references which were not yet available during the initial
|
resolves references which were not yet available during the initial
|
||||||
parsing phase.
|
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.
|
"""Initialize a WSDL document.
|
||||||
|
|
||||||
The root definition properties are exposed as entry points.
|
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 isinstance(location, six.string_types):
|
||||||
if is_relative_path(location):
|
if is_relative_path(location):
|
||||||
|
@ -55,12 +66,17 @@ class Document(object):
|
||||||
self.location = base
|
self.location = base
|
||||||
|
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
|
self.strict = strict
|
||||||
|
|
||||||
# Dict with all definition objects within this WSDL
|
# Dict with all definition objects within this WSDL
|
||||||
self._definitions = {}
|
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 = Definition(self, document, self.location)
|
||||||
root_definitions.resolve_imports()
|
root_definitions.resolve_imports()
|
||||||
|
@ -75,8 +91,6 @@ class Document(object):
|
||||||
return '<WSDL(location=%r)>' % self.location
|
return '<WSDL(location=%r)>' % self.location
|
||||||
|
|
||||||
def dump(self):
|
def dump(self):
|
||||||
namespaces = {v: k for k, v in self.types.prefix_map.items()}
|
|
||||||
|
|
||||||
print('')
|
print('')
|
||||||
print("Prefixes:")
|
print("Prefixes:")
|
||||||
for prefix, namespace in self.types.prefix_map.items():
|
for prefix, namespace in self.types.prefix_map.items():
|
||||||
|
@ -84,18 +98,14 @@ class Document(object):
|
||||||
|
|
||||||
print('')
|
print('')
|
||||||
print("Global elements:")
|
print("Global elements:")
|
||||||
for elm_obj in sorted(self.types.elements, key=lambda k: six.text_type(k)):
|
for elm_obj in sorted(self.types.elements, key=lambda k: k.qname):
|
||||||
value = six.text_type(elm_obj)
|
value = elm_obj.signature(schema=self.types)
|
||||||
if hasattr(elm_obj, 'qname') and elm_obj.qname.namespace:
|
|
||||||
value = '%s:%s' % (namespaces[elm_obj.qname.namespace], value)
|
|
||||||
print(' ' * 4, value)
|
print(' ' * 4, value)
|
||||||
|
|
||||||
print('')
|
print('')
|
||||||
print("Global types:")
|
print("Global types:")
|
||||||
for type_obj in sorted(self.types.types, key=lambda k: k.qname or ''):
|
for type_obj in sorted(self.types.types, key=lambda k: k.qname or ''):
|
||||||
value = six.text_type(type_obj)
|
value = type_obj.signature(schema=self.types)
|
||||||
if getattr(type_obj, 'qname', None) and type_obj.qname.namespace:
|
|
||||||
value = '%s:%s' % (namespaces[type_obj.qname.namespace], value)
|
|
||||||
print(' ' * 4, value)
|
print(' ' * 4, value)
|
||||||
|
|
||||||
print('')
|
print('')
|
||||||
|
@ -118,7 +128,7 @@ class Document(object):
|
||||||
print('%s%s' % (' ' * 12, six.text_type(operation)))
|
print('%s%s' % (' ' * 12, six.text_type(operation)))
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
def _load_content(self, location):
|
def _get_xml_document(self, location):
|
||||||
"""Load the XML content from the given location and return an
|
"""Load the XML content from the given location and return an
|
||||||
lxml.Element object.
|
lxml.Element object.
|
||||||
|
|
||||||
|
@ -126,9 +136,8 @@ class Document(object):
|
||||||
:type location: string
|
:type location: string
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if hasattr(location, 'read'):
|
return load_external(
|
||||||
return parse_xml(location.read())
|
location, self.transport, self.location, strict=self.strict)
|
||||||
return load_external(location, self.transport, self.location)
|
|
||||||
|
|
||||||
def _add_definition(self, definition):
|
def _add_definition(self, definition):
|
||||||
key = (definition.target_namespace, definition.location)
|
key = (definition.target_namespace, definition.location)
|
||||||
|
@ -136,9 +145,18 @@ class Document(object):
|
||||||
|
|
||||||
|
|
||||||
class Definition(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):
|
def __init__(self, wsdl, doc, location):
|
||||||
|
"""fo
|
||||||
|
|
||||||
|
:param wsdl: The wsdl
|
||||||
|
|
||||||
|
"""
|
||||||
logger.debug("Creating definition for %s", location)
|
logger.debug("Creating definition for %s", location)
|
||||||
self.wsdl = wsdl
|
self.wsdl = wsdl
|
||||||
self.location = location
|
self.location = location
|
||||||
|
@ -183,7 +201,16 @@ class Definition(object):
|
||||||
try:
|
try:
|
||||||
return definition.get(name, key, _processed)
|
return definition.get(name, key, _processed)
|
||||||
except IndexError:
|
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))
|
raise IndexError("No definition %r in %r found" % (key, name))
|
||||||
|
|
||||||
def resolve_imports(self):
|
def resolve_imports(self):
|
||||||
|
@ -234,7 +261,7 @@ class Definition(object):
|
||||||
if key in self.wsdl._definitions:
|
if key in self.wsdl._definitions:
|
||||||
self.imports[key] = self.wsdl._definitions[key]
|
self.imports[key] = self.wsdl._definitions[key]
|
||||||
else:
|
else:
|
||||||
document = self.wsdl._load_content(location)
|
document = self.wsdl._get_xml_document(location)
|
||||||
if etree.QName(document.tag).localname == 'schema':
|
if etree.QName(document.tag).localname == 'schema':
|
||||||
self.types.add_documents([document], location)
|
self.types.add_documents([document], location)
|
||||||
else:
|
else:
|
||||||
|
@ -251,6 +278,8 @@ class Definition(object):
|
||||||
If the wsdl:types doesn't container an xml schema then an empty schema
|
If the wsdl:types doesn't container an xml schema then an empty schema
|
||||||
is returned instead.
|
is returned instead.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<definitions .... >
|
<definitions .... >
|
||||||
<types>
|
<types>
|
||||||
<xsd:schema .... />*
|
<xsd:schema .... />*
|
||||||
|
@ -279,6 +308,9 @@ class Definition(object):
|
||||||
|
|
||||||
def parse_messages(self, doc):
|
def parse_messages(self, doc):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<definitions .... >
|
<definitions .... >
|
||||||
<message name="nmtoken"> *
|
<message name="nmtoken"> *
|
||||||
<part name="nmtoken" element="qname"? type="qname"?/> *
|
<part name="nmtoken" element="qname"? type="qname"?/> *
|
||||||
|
@ -291,14 +323,20 @@ class Definition(object):
|
||||||
"""
|
"""
|
||||||
result = {}
|
result = {}
|
||||||
for msg_node in doc.findall("wsdl:message", namespaces=NSMAP):
|
for msg_node in doc.findall("wsdl:message", namespaces=NSMAP):
|
||||||
msg = parse.parse_abstract_message(self, msg_node)
|
try:
|
||||||
result[msg.name.text] = msg
|
msg = parse.parse_abstract_message(self, msg_node)
|
||||||
logger.debug("Adding message: %s", msg.name.text)
|
except IncompleteMessage as exc:
|
||||||
|
warnings.warn(str(exc))
|
||||||
|
else:
|
||||||
|
result[msg.name.text] = msg
|
||||||
|
logger.debug("Adding message: %s", msg.name.text)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def parse_ports(self, doc):
|
def parse_ports(self, doc):
|
||||||
"""Return dict with `PortType` instances as values
|
"""Return dict with `PortType` instances as values
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<wsdl:definitions .... >
|
<wsdl:definitions .... >
|
||||||
<wsdl:portType name="nmtoken">
|
<wsdl:portType name="nmtoken">
|
||||||
<wsdl:operation 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
|
HTTP Post. The detection of the type of bindings is done by the
|
||||||
bindings themselves using the introspection of the xml nodes.
|
bindings themselves using the introspection of the xml nodes.
|
||||||
|
|
||||||
XML Structure::
|
Definition::
|
||||||
|
|
||||||
<wsdl:definitions .... >
|
<wsdl:definitions .... >
|
||||||
<wsdl:binding name="nmtoken" type="qname"> *
|
<wsdl:binding name="nmtoken" type="qname"> *
|
||||||
|
@ -345,6 +383,9 @@ class Definition(object):
|
||||||
|
|
||||||
:param doc: The source document
|
:param doc: The source document
|
||||||
:type doc: lxml.etree._Element
|
:type doc: lxml.etree._Element
|
||||||
|
:returns: Dictionary with binding name as key and Binding instance as
|
||||||
|
value
|
||||||
|
:rtype: dict
|
||||||
|
|
||||||
"""
|
"""
|
||||||
result = {}
|
result = {}
|
||||||
|
@ -382,6 +423,9 @@ class Definition(object):
|
||||||
|
|
||||||
def parse_service(self, doc):
|
def parse_service(self, doc):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<wsdl:definitions .... >
|
<wsdl:definitions .... >
|
||||||
<wsdl:service .... > *
|
<wsdl:service .... > *
|
||||||
<wsdl:port name="nmtoken" binding="qname"> *
|
<wsdl:port name="nmtoken" binding="qname"> *
|
||||||
|
|
|
@ -11,38 +11,63 @@ module.
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from lxml.etree import QName
|
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:
|
try:
|
||||||
import xmlsec
|
import xmlsec
|
||||||
except ImportError:
|
except ImportError:
|
||||||
xmlsec = None
|
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 envelope
|
||||||
SOAP_NS = 'http://schemas.xmlsoap.org/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()
|
||||||
|
|
||||||
class Signature(object):
|
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 MemorySignature(object):
|
||||||
"""Sign given SOAP envelope with WSSE sig using given key and cert."""
|
"""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()
|
check_xmlsec_import()
|
||||||
|
|
||||||
self.key_file = key_file
|
self.key_data = key_data
|
||||||
self.certfile = certfile
|
self.cert_data = cert_data
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
||||||
def apply(self, envelope, headers):
|
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
|
return envelope, headers
|
||||||
|
|
||||||
def verify(self, envelope):
|
def verify(self, envelope):
|
||||||
verify_envelope(envelope, self.certfile)
|
key = _make_verify_key(self.cert_data)
|
||||||
|
_verify_envelope_with_key(envelope, key)
|
||||||
return envelope
|
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():
|
def check_xmlsec_import():
|
||||||
if xmlsec is None:
|
if xmlsec is None:
|
||||||
|
@ -141,6 +166,12 @@ def sign_envelope(envelope, keyfile, certfile, password=None):
|
||||||
</soap:Envelope>
|
</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.
|
# Create the Signature node.
|
||||||
signature = xmlsec.template.create(
|
signature = xmlsec.template.create(
|
||||||
envelope,
|
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_issuer_serial(x509_data)
|
||||||
xmlsec.template.x509_data_add_certificate(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.
|
# Insert the Signature node in the wsse:Security header.
|
||||||
security = get_security_header(envelope)
|
security = get_security_header(envelope)
|
||||||
security.insert(0, signature)
|
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()``
|
Expects a document like that found in the sample XML in the ``sign()``
|
||||||
docstring.
|
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)
|
soap_env = detect_soap_env(envelope)
|
||||||
|
|
||||||
header = envelope.find(QName(soap_env, 'Header'))
|
header = envelope.find(QName(soap_env, 'Header'))
|
||||||
|
if not header:
|
||||||
|
raise SignatureVerificationFailed()
|
||||||
|
|
||||||
security = header.find(QName(ns.WSSE, 'Security'))
|
security = header.find(QName(ns.WSSE, 'Security'))
|
||||||
signature = security.find(QName(ns.DS, 'Signature'))
|
signature = security.find(QName(ns.DS, 'Signature'))
|
||||||
|
|
||||||
|
@ -213,7 +247,6 @@ def verify_envelope(envelope, certfile):
|
||||||
)[0]
|
)[0]
|
||||||
ctx.register_id(referenced, 'Id', ns.WSU)
|
ctx.register_id(referenced, 'Id', ns.WSU)
|
||||||
|
|
||||||
key = xmlsec.Key.from_file(certfile, xmlsec.KeyFormat.CERT_PEM, None)
|
|
||||||
ctx.key = key
|
ctx.key = key
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from uuid import uuid4
|
|
||||||
from lxml import etree
|
|
||||||
import datetime
|
import datetime
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
from lxml import etree
|
||||||
from lxml.builder import ElementMaker
|
from lxml.builder import ElementMaker
|
||||||
|
|
||||||
from zeep import ns
|
from zeep import ns
|
||||||
|
|
|
@ -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.elements import * # noqa
|
||||||
from zeep.xsd.schema import Schema # noqa
|
from zeep.xsd.schema import Schema # noqa
|
||||||
from zeep.xsd.types import * # noqa
|
from zeep.xsd.types import * # noqa
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
|
from zeep import ns
|
||||||
NS_XSD = 'http://www.w3.org/2001/XMLSchema'
|
|
||||||
|
|
||||||
|
|
||||||
def xsi_ns(localname):
|
def xsi_ns(localname):
|
||||||
return etree.QName(NS_XSI, localname)
|
return etree.QName(ns.XSI, localname)
|
||||||
|
|
||||||
|
|
||||||
def xsd_ns(localname):
|
def xsd_ns(localname):
|
||||||
return etree.QName(NS_XSD, localname)
|
return etree.QName(ns.XSD, localname)
|
||||||
|
|
||||||
|
|
||||||
class _StaticIdentity(object):
|
class _StaticIdentity(object):
|
||||||
|
@ -22,3 +20,4 @@ class _StaticIdentity(object):
|
||||||
|
|
||||||
NotSet = _StaticIdentity('NotSet')
|
NotSet = _StaticIdentity('NotSet')
|
||||||
SkipValue = _StaticIdentity('SkipValue')
|
SkipValue = _StaticIdentity('SkipValue')
|
||||||
|
Nil = _StaticIdentity('Nil')
|
||||||
|
|
|
@ -2,9 +2,9 @@ import logging
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from zeep import exceptions
|
from zeep import exceptions, ns
|
||||||
from zeep.utils import qname_attr
|
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.elements.base import Base
|
||||||
from zeep.xsd.utils import max_occurs_iter
|
from zeep.xsd.utils import max_occurs_iter
|
||||||
from zeep.xsd.valueobjects import AnyObject
|
from zeep.xsd.valueobjects import AnyObject
|
||||||
|
@ -84,7 +84,19 @@ class Any(Base):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
|
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 = []
|
result = []
|
||||||
|
|
||||||
for _unused in max_occurs_iter(self.max_occurs):
|
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
|
# Check if we received a proper value object. If we receive the wrong
|
||||||
# type then return a nice error message
|
# type then return a nice error message
|
||||||
if self.restrict:
|
if self.restrict:
|
||||||
expected_types = (etree._Element,) + self.restrict.accepted_types
|
expected_types = (etree._Element, dict,) + self.restrict.accepted_types
|
||||||
else:
|
else:
|
||||||
expected_types = (etree._Element, AnyObject)
|
expected_types = (etree._Element, dict,AnyObject)
|
||||||
|
|
||||||
if not isinstance(value, expected_types):
|
if not isinstance(value, expected_types):
|
||||||
type_names = [
|
type_names = [
|
||||||
'%s.%s' % (t.__module__, t.__name__) for t in expected_types
|
'%s.%s' % (t.__module__, t.__name__) for t in expected_types
|
||||||
|
@ -188,7 +201,7 @@ class Any(Base):
|
||||||
def resolve(self):
|
def resolve(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=True):
|
||||||
if self.restrict:
|
if self.restrict:
|
||||||
base = self.restrict.name
|
base = self.restrict.name
|
||||||
else:
|
else:
|
||||||
|
@ -201,23 +214,30 @@ class Any(Base):
|
||||||
|
|
||||||
class AnyAttribute(Base):
|
class AnyAttribute(Base):
|
||||||
name = None
|
name = None
|
||||||
|
_ignore_attributes = [
|
||||||
|
etree.QName(ns.XSI, 'type')
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, process_contents='strict'):
|
def __init__(self, process_contents='strict'):
|
||||||
self.qname = None
|
self.qname = None
|
||||||
self.process_contents = process_contents
|
self.process_contents = process_contents
|
||||||
|
|
||||||
def parse(self, attributes, context=None):
|
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):
|
def resolve(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def render(self, parent, value, render_path=None):
|
def render(self, parent, value, render_path=None):
|
||||||
if value is None:
|
if value in (None, NotSet):
|
||||||
return
|
return
|
||||||
|
|
||||||
for name, val in value.items():
|
for name, val in value.items():
|
||||||
parent.set(name, val)
|
parent.set(name, val)
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=True):
|
||||||
return '{}'
|
return '{}'
|
||||||
|
|
|
@ -88,5 +88,5 @@ class AttributeGroup(object):
|
||||||
self._attributes = resolved
|
self._attributes = resolved
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=True):
|
||||||
return ', '.join(attr.signature() for attr in self._attributes)
|
return ', '.join(attr.signature(schema) for attr in self._attributes)
|
||||||
|
|
|
@ -25,8 +25,20 @@ class Base(object):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=False):
|
||||||
return ''
|
return ''
|
||||||
|
|
|
@ -35,6 +35,6 @@ class Schema(Base):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
default_elements = {
|
_elements = [
|
||||||
xsd_ns('schema'): Schema(),
|
Schema
|
||||||
}
|
]
|
||||||
|
|
|
@ -6,10 +6,10 @@ from lxml import etree
|
||||||
from zeep import exceptions
|
from zeep import exceptions
|
||||||
from zeep.exceptions import UnexpectedElementError
|
from zeep.exceptions import UnexpectedElementError
|
||||||
from zeep.utils import qname_attr
|
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.context import XmlParserContext
|
||||||
from zeep.xsd.elements.base import Base
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -38,7 +38,10 @@ class Element(Base):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.type:
|
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
|
return '%s()' % self.name
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
|
@ -57,10 +60,16 @@ class Element(Base):
|
||||||
self.__class__ == other.__class__ and
|
self.__class__ == other.__class__ and
|
||||||
self.__dict__ == other.__dict__)
|
self.__dict__ == other.__dict__)
|
||||||
|
|
||||||
|
def get_prefixed_name(self, schema):
|
||||||
|
return create_prefixed_name(self.qname, schema)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_value(self):
|
def default_value(self):
|
||||||
value = [] if self.accepts_multiple else self.default
|
if self.accepts_multiple:
|
||||||
return value
|
return []
|
||||||
|
if self.is_optional:
|
||||||
|
return None
|
||||||
|
return self.default
|
||||||
|
|
||||||
def clone(self, name=None, min_occurs=1, max_occurs=1):
|
def clone(self, name=None, min_occurs=1, max_occurs=1):
|
||||||
new = copy.copy(self)
|
new = copy.copy(self)
|
||||||
|
@ -81,6 +90,18 @@ class Element(Base):
|
||||||
use that for further processing. This should only be done for subtypes
|
use that for further processing. This should only be done for subtypes
|
||||||
of the defined type but for now we just accept everything.
|
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()
|
context = context or XmlParserContext()
|
||||||
instance_type = qname_attr(xmlelement, xsi_ns('type'))
|
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 = schema.get_type(instance_type, fail_silently=True)
|
||||||
xsd_type = xsd_type or self.type
|
xsd_type = xsd_type or self.type
|
||||||
return xsd_type.parse_xmlelement(
|
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):
|
def parse_kwargs(self, kwargs, name, available_kwargs):
|
||||||
return self.type.parse_kwargs(
|
return self.type.parse_kwargs(
|
||||||
kwargs, name or self.attr_name, available_kwargs)
|
kwargs, name or self.attr_name, available_kwargs)
|
||||||
|
|
||||||
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
|
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 = []
|
result = []
|
||||||
num_matches = 0
|
num_matches = 0
|
||||||
for _unused in max_occurs_iter(self.max_occurs):
|
for _unused in max_occurs_iter(self.max_occurs):
|
||||||
|
@ -113,7 +147,8 @@ class Element(Base):
|
||||||
element_tag = etree.QName(xmlelements[0].tag)
|
element_tag = etree.QName(xmlelements[0].tag)
|
||||||
if (
|
if (
|
||||||
element_tag.namespace and self.qname.namespace and
|
element_tag.namespace and self.qname.namespace and
|
||||||
element_tag.namespace != self.qname.namespace
|
element_tag.namespace != self.qname.namespace and
|
||||||
|
schema.strict
|
||||||
):
|
):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -123,8 +158,7 @@ class Element(Base):
|
||||||
num_matches += 1
|
num_matches += 1
|
||||||
item = self.parse(
|
item = self.parse(
|
||||||
xmlelement, schema, allow_none=True, context=context)
|
xmlelement, schema, allow_none=True, context=context)
|
||||||
if item is not None:
|
result.append(item)
|
||||||
result.append(item)
|
|
||||||
else:
|
else:
|
||||||
# If the element passed doesn't match and the current one is
|
# If the element passed doesn't match and the current one is
|
||||||
# not optional then throw an error
|
# not optional then throw an error
|
||||||
|
@ -158,6 +192,12 @@ class Element(Base):
|
||||||
|
|
||||||
def _render_value_item(self, parent, value, render_path):
|
def _render_value_item(self, parent, value, render_path):
|
||||||
"""Render the value on the parent lxml.Element"""
|
"""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 value is None or value is NotSet:
|
||||||
if self.is_optional:
|
if self.is_optional:
|
||||||
return
|
return
|
||||||
|
@ -215,11 +255,19 @@ class Element(Base):
|
||||||
self.resolve_type()
|
self.resolve_type()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=True):
|
||||||
if len(depth) > 0 and self.is_global:
|
from zeep.xsd import ComplexType
|
||||||
return self.name + '()'
|
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:
|
if self.accepts_multiple:
|
||||||
return '%s[]' % value
|
return '%s[]' % value
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -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 copy
|
||||||
import operator
|
import operator
|
||||||
from collections import OrderedDict, defaultdict, deque
|
from collections import OrderedDict, defaultdict, deque
|
||||||
|
|
||||||
from cached_property import threaded_cached_property
|
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.const import NotSet, SkipValue
|
||||||
from zeep.xsd.elements import Any, Element
|
from zeep.xsd.elements import Any, Element
|
||||||
from zeep.xsd.elements.base import Base
|
from zeep.xsd.elements.base import Base
|
||||||
from zeep.xsd.utils import (
|
from zeep.xsd.utils import (
|
||||||
NamePrefixGenerator, UniqueNameGenerator, max_occurs_iter)
|
NamePrefixGenerator, UniqueNameGenerator, create_prefixed_name,
|
||||||
|
max_occurs_iter)
|
||||||
|
|
||||||
__all__ = ['All', 'Choice', 'Group', 'Sequence']
|
__all__ = ['All', 'Choice', 'Group', 'Sequence']
|
||||||
|
|
||||||
|
|
||||||
class Indicator(Base):
|
class Indicator(Base):
|
||||||
|
"""Base class for the other indicators"""
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s(%s)>' % (
|
return '<%s(%s)>' % (
|
||||||
self.__class__.__name__, super(Indicator, self).__repr__())
|
self.__class__.__name__, super(Indicator, self).__repr__())
|
||||||
|
|
||||||
@threaded_cached_property
|
@property
|
||||||
def default_value(self):
|
def default_value(self):
|
||||||
return OrderedDict([
|
values = OrderedDict([
|
||||||
(name, element.default_value) for name, element in self.elements
|
(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):
|
def clone(self, name, min_occurs=1, max_occurs=1):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class OrderIndicator(Indicator, list):
|
class OrderIndicator(Indicator, list):
|
||||||
|
"""Base class for All, Choice and Sequence classes."""
|
||||||
name = None
|
name = None
|
||||||
|
|
||||||
def __init__(self, elements=None, min_occurs=1, max_occurs=1):
|
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.
|
If not all required elements are available then 0 is returned.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
num = 0
|
if not self.accepts_multiple:
|
||||||
for name, element in self.elements_nested:
|
values = [values]
|
||||||
if isinstance(element, Element):
|
|
||||||
if element.name in values and values[element.name] is not None:
|
results = set()
|
||||||
num += 1
|
for value in values:
|
||||||
else:
|
num = 0
|
||||||
num += element.accept(values)
|
for name, element in self.elements_nested:
|
||||||
return num
|
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):
|
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 = {}
|
result = {}
|
||||||
for name, element in self.elements:
|
for name, element in self.elements:
|
||||||
if index >= len(args):
|
if index >= len(args):
|
||||||
|
@ -110,11 +144,24 @@ class OrderIndicator(Indicator, list):
|
||||||
The available_kwargs is modified in-place. Returns a dict with the
|
The available_kwargs is modified in-place. Returns a dict with the
|
||||||
result.
|
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:
|
if self.accepts_multiple:
|
||||||
assert name
|
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
|
# Make sure we have a list, lame lame
|
||||||
item_kwargs = kwargs.get(name)
|
item_kwargs = kwargs.get(name)
|
||||||
|
@ -123,19 +170,26 @@ class OrderIndicator(Indicator, list):
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for item_value in max_occurs_iter(self.max_occurs, item_kwargs):
|
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()
|
subresult = OrderedDict()
|
||||||
for item_name, element in self.elements:
|
for item_name, element in self.elements:
|
||||||
value = element.parse_kwargs(item_value, item_name, item_kwargs)
|
value = element.parse_kwargs(item_value, item_name, item_kwargs)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
subresult.update(value)
|
subresult.update(value)
|
||||||
|
|
||||||
|
if item_kwargs:
|
||||||
|
raise TypeError((
|
||||||
|
"%s() got an unexpected keyword argument %r."
|
||||||
|
) % (self, list(item_kwargs)[0]))
|
||||||
|
|
||||||
result.append(subresult)
|
result.append(subresult)
|
||||||
|
|
||||||
if self.accepts_multiple:
|
result = {name: result}
|
||||||
result = {name: result}
|
|
||||||
else:
|
|
||||||
result = result[0] if result else None
|
|
||||||
|
|
||||||
# All items consumed
|
# All items consumed
|
||||||
if not any(filter(None, item_kwargs)):
|
if not any(filter(None, item_kwargs)):
|
||||||
|
@ -144,15 +198,13 @@ class OrderIndicator(Indicator, list):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
assert not self.accepts_multiple
|
||||||
result = OrderedDict()
|
result = OrderedDict()
|
||||||
for elm_name, element in self.elements_nested:
|
for elm_name, element in self.elements_nested:
|
||||||
sub_result = element.parse_kwargs(kwargs, elm_name, available_kwargs)
|
sub_result = element.parse_kwargs(kwargs, elm_name, available_kwargs)
|
||||||
if sub_result:
|
if sub_result:
|
||||||
result.update(sub_result)
|
result.update(sub_result)
|
||||||
|
|
||||||
if name:
|
|
||||||
result = {name: result}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def resolve(self):
|
def resolve(self):
|
||||||
|
@ -167,6 +219,8 @@ class OrderIndicator(Indicator, list):
|
||||||
else:
|
else:
|
||||||
values = value
|
values = value
|
||||||
|
|
||||||
|
self.validate(values, render_path)
|
||||||
|
|
||||||
for value in max_occurs_iter(self.max_occurs, values):
|
for value in max_occurs_iter(self.max_occurs, values):
|
||||||
for name, element in self.elements_nested:
|
for name, element in self.elements_nested:
|
||||||
if name:
|
if name:
|
||||||
|
@ -186,22 +240,20 @@ class OrderIndicator(Indicator, list):
|
||||||
if element_value is not None or not element.is_optional:
|
if element_value is not None or not element.is_optional:
|
||||||
element.render(parent, element_value, child_path)
|
element.render(parent, element_value, child_path)
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def validate(self, value, render_path):
|
||||||
"""
|
for item in value:
|
||||||
Use a tuple of element names as depth indicator, so that when an element is repeated,
|
if item is NotSet:
|
||||||
do not try to create its signature, as it would lead to infinite recursion
|
raise ValidationError("No value set", path=render_path)
|
||||||
"""
|
|
||||||
depth += (self.name,)
|
def signature(self, schema=None, standalone=True):
|
||||||
parts = []
|
parts = []
|
||||||
for name, element in self.elements_nested:
|
for name, element in self.elements_nested:
|
||||||
if hasattr(element, 'type') and element.type.name and element.type.name in depth:
|
if isinstance(element, Indicator):
|
||||||
parts.append('{}: {}'.format(name, element.type.name))
|
parts.append(element.signature(schema, standalone=False))
|
||||||
elif name:
|
|
||||||
parts.append('%s: %s' % (name, element.signature(depth)))
|
|
||||||
elif isinstance(element, Indicator):
|
|
||||||
parts.append('%s' % (element.signature(depth)))
|
|
||||||
else:
|
else:
|
||||||
parts.append('%s: %s' % (name, element.signature(depth)))
|
value = element.signature(schema, standalone=False)
|
||||||
|
parts.append('%s: %s' % (name, value))
|
||||||
|
|
||||||
part = ', '.join(parts)
|
part = ', '.join(parts)
|
||||||
|
|
||||||
if self.accepts_multiple:
|
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):
|
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()
|
result = OrderedDict()
|
||||||
expected_tags = {element.qname for __, element in self.elements}
|
expected_tags = {element.qname for __, element in self.elements}
|
||||||
consumed_tags = set()
|
consumed_tags = set()
|
||||||
|
@ -236,10 +306,18 @@ class All(OrderIndicator):
|
||||||
result[name] = element.parse_xmlelements(
|
result[name] = element.parse_xmlelements(
|
||||||
sub_elements, schema, context=context)
|
sub_elements, schema, context=context)
|
||||||
|
|
||||||
|
if self._consume_other and xmlelements:
|
||||||
|
result['_raw_elements'] = list(xmlelements)
|
||||||
|
xmlelements.clear()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class Choice(OrderIndicator):
|
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
|
@property
|
||||||
def is_optional(self):
|
def is_optional(self):
|
||||||
|
@ -250,49 +328,59 @@ class Choice(OrderIndicator):
|
||||||
return OrderedDict()
|
return OrderedDict()
|
||||||
|
|
||||||
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
|
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 = []
|
result = []
|
||||||
|
|
||||||
for _unused in max_occurs_iter(self.max_occurs):
|
for _unused in max_occurs_iter(self.max_occurs):
|
||||||
if not xmlelements:
|
if not xmlelements:
|
||||||
break
|
break
|
||||||
|
|
||||||
for node in list(xmlelements):
|
# Choose out of multiple
|
||||||
|
options = []
|
||||||
|
for element_name, element in self.elements_nested:
|
||||||
|
|
||||||
# Choose out of multiple
|
local_xmlelements = copy.copy(xmlelements)
|
||||||
options = []
|
|
||||||
for element_name, element in self.elements_nested:
|
|
||||||
|
|
||||||
local_xmlelements = copy.copy(xmlelements)
|
try:
|
||||||
|
sub_result = element.parse_xmlelements(
|
||||||
|
xmlelements=local_xmlelements,
|
||||||
|
schema=schema,
|
||||||
|
name=element_name,
|
||||||
|
context=context)
|
||||||
|
except UnexpectedElementError:
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
if isinstance(element, Element):
|
||||||
sub_result = element.parse_xmlelements(
|
sub_result = {element_name: sub_result}
|
||||||
local_xmlelements, schema, context=context)
|
|
||||||
except UnexpectedElementError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(element, OrderIndicator):
|
num_consumed = len(xmlelements) - len(local_xmlelements)
|
||||||
if element.accepts_multiple:
|
if num_consumed:
|
||||||
sub_result = {element_name: sub_result}
|
options.append((num_consumed, sub_result))
|
||||||
else:
|
|
||||||
sub_result = {element_name: sub_result}
|
|
||||||
|
|
||||||
num_consumed = len(xmlelements) - len(local_xmlelements)
|
if not options:
|
||||||
if num_consumed:
|
xmlelements = []
|
||||||
options.append((num_consumed, sub_result))
|
break
|
||||||
|
|
||||||
if not options:
|
# Sort on least left
|
||||||
xmlelements = []
|
options = sorted(options, key=operator.itemgetter(0), reverse=True)
|
||||||
break
|
if options:
|
||||||
|
result.append(options[0][1])
|
||||||
# Sort on least left
|
for i in range(options[0][0]):
|
||||||
options = sorted(options, key=operator.itemgetter(0), reverse=True)
|
xmlelements.popleft()
|
||||||
if options:
|
else:
|
||||||
result.append(options[0][1])
|
break
|
||||||
for i in range(options[0][0]):
|
|
||||||
xmlelements.popleft()
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
if self.accepts_multiple:
|
if self.accepts_multiple:
|
||||||
result = {name: result}
|
result = {name: result}
|
||||||
|
@ -308,7 +396,7 @@ class Choice(OrderIndicator):
|
||||||
This handles two distinct initialization methods:
|
This handles two distinct initialization methods:
|
||||||
|
|
||||||
1. Passing the choice elements directly to the kwargs (unnested)
|
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.
|
This case is required when multiple choice elements are given.
|
||||||
|
|
||||||
:param name: Name of the choice element (_value_1)
|
:param name: Name of the choice element (_value_1)
|
||||||
|
@ -320,6 +408,8 @@ class Choice(OrderIndicator):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if name and name in available_kwargs:
|
if name and name in available_kwargs:
|
||||||
|
assert self.accepts_multiple
|
||||||
|
|
||||||
values = kwargs[name] or []
|
values = kwargs[name] or []
|
||||||
available_kwargs.remove(name)
|
available_kwargs.remove(name)
|
||||||
result = []
|
result = []
|
||||||
|
@ -327,9 +417,9 @@ class Choice(OrderIndicator):
|
||||||
if isinstance(values, dict):
|
if isinstance(values, dict):
|
||||||
values = [values]
|
values = [values]
|
||||||
|
|
||||||
|
# TODO: Use most greedy choice instead of first matching
|
||||||
for value in values:
|
for value in values:
|
||||||
for element in self:
|
for element in self:
|
||||||
# TODO: Use most greedy choice instead of first matching
|
|
||||||
if isinstance(element, OrderIndicator):
|
if isinstance(element, OrderIndicator):
|
||||||
choice_value = value[name] if name in value else value
|
choice_value = value[name] if name in value else value
|
||||||
if element.accept(choice_value):
|
if element.accept(choice_value):
|
||||||
|
@ -356,9 +446,9 @@ class Choice(OrderIndicator):
|
||||||
|
|
||||||
# When choice elements are specified directly in the kwargs
|
# When choice elements are specified directly in the kwargs
|
||||||
found = False
|
found = False
|
||||||
for i, choice in enumerate(self):
|
for name, choice in self.elements_nested:
|
||||||
temp_kwargs = copy.copy(available_kwargs)
|
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 subresult:
|
||||||
if not any(subresult.values()):
|
if not any(subresult.values()):
|
||||||
|
@ -388,12 +478,24 @@ class Choice(OrderIndicator):
|
||||||
if not self.accepts_multiple:
|
if not self.accepts_multiple:
|
||||||
value = [value]
|
value = [value]
|
||||||
|
|
||||||
|
self.validate(value, render_path)
|
||||||
|
|
||||||
for item in value:
|
for item in value:
|
||||||
result = self._find_element_to_render(item)
|
result = self._find_element_to_render(item)
|
||||||
if result:
|
if result:
|
||||||
element, choice_value = result
|
element, choice_value = result
|
||||||
element.render(parent, choice_value, render_path)
|
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):
|
def accept(self, values):
|
||||||
"""Return the number of values which are accepted by this choice.
|
"""Return the number of values which are accepted by this choice.
|
||||||
|
|
||||||
|
@ -403,15 +505,24 @@ class Choice(OrderIndicator):
|
||||||
nums = set()
|
nums = set()
|
||||||
for name, element in self.elements_nested:
|
for name, element in self.elements_nested:
|
||||||
if isinstance(element, Element):
|
if isinstance(element, Element):
|
||||||
if name in values and values[name]:
|
if self.accepts_multiple:
|
||||||
nums.add(1)
|
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:
|
else:
|
||||||
num = element.accept(values)
|
num = element.accept(values)
|
||||||
nums.add(num)
|
nums.add(num)
|
||||||
return max(nums) if nums else 0
|
return max(nums) if nums else 0
|
||||||
|
|
||||||
def _find_element_to_render(self, value):
|
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 = []
|
matches = []
|
||||||
|
|
||||||
for name, element in self.elements_nested:
|
for name, element in self.elements_nested:
|
||||||
|
@ -441,13 +552,13 @@ class Choice(OrderIndicator):
|
||||||
matches = sorted(matches, key=operator.itemgetter(0), reverse=True)
|
matches = sorted(matches, key=operator.itemgetter(0), reverse=True)
|
||||||
return matches[0][1:]
|
return matches[0][1:]
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=True):
|
||||||
parts = []
|
parts = []
|
||||||
for name, element in self.elements_nested:
|
for name, element in self.elements_nested:
|
||||||
if isinstance(element, OrderIndicator):
|
if isinstance(element, OrderIndicator):
|
||||||
parts.append('{%s}' % (element.signature(depth)))
|
parts.append('{%s}' % (element.signature(schema, standalone=False)))
|
||||||
else:
|
else:
|
||||||
parts.append('{%s: %s}' % (name, element.signature(depth)))
|
parts.append('{%s: %s}' % (name, element.signature(schema, standalone=False)))
|
||||||
part = '(%s)' % ' | '.join(parts)
|
part = '(%s)' % ' | '.join(parts)
|
||||||
if self.accepts_multiple:
|
if self.accepts_multiple:
|
||||||
return '%s[]' % (part,)
|
return '%s[]' % (part,)
|
||||||
|
@ -455,17 +566,42 @@ class Choice(OrderIndicator):
|
||||||
|
|
||||||
|
|
||||||
class Sequence(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):
|
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 = []
|
result = []
|
||||||
|
|
||||||
|
if self.accepts_multiple:
|
||||||
|
assert name
|
||||||
|
|
||||||
for _unused in max_occurs_iter(self.max_occurs):
|
for _unused in max_occurs_iter(self.max_occurs):
|
||||||
if not xmlelements:
|
if not xmlelements:
|
||||||
break
|
break
|
||||||
|
|
||||||
item_result = OrderedDict()
|
item_result = OrderedDict()
|
||||||
for elm_name, element in self.elements:
|
for elm_name, element in self.elements:
|
||||||
item_subresult = element.parse_xmlelements(
|
try:
|
||||||
xmlelements, schema, name, context=context)
|
item_subresult = element.parse_xmlelements(
|
||||||
|
xmlelements, schema, name, context=context)
|
||||||
|
except UnexpectedElementError:
|
||||||
|
if schema.strict:
|
||||||
|
raise
|
||||||
|
item_subresult = None
|
||||||
|
|
||||||
# Unwrap if allowed
|
# Unwrap if allowed
|
||||||
if isinstance(element, OrderIndicator):
|
if isinstance(element, OrderIndicator):
|
||||||
|
@ -480,7 +616,6 @@ class Sequence(OrderIndicator):
|
||||||
|
|
||||||
if not self.accepts_multiple:
|
if not self.accepts_multiple:
|
||||||
return result[0] if result else None
|
return result[0] if result else None
|
||||||
|
|
||||||
return {name: result}
|
return {name: result}
|
||||||
|
|
||||||
|
|
||||||
|
@ -498,15 +633,8 @@ class Group(Indicator):
|
||||||
self.max_occurs = max_occurs
|
self.max_occurs = max_occurs
|
||||||
self.min_occurs = min_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):
|
def __str__(self):
|
||||||
return '%s(%s)' % (self.name, self.signature())
|
return self.signature()
|
||||||
|
|
||||||
def __iter__(self, *args, **kwargs):
|
def __iter__(self, *args, **kwargs):
|
||||||
for item in self.child:
|
for item in self.child:
|
||||||
|
@ -518,13 +646,28 @@ class Group(Indicator):
|
||||||
return [('_value_1', self.child)]
|
return [('_value_1', self.child)]
|
||||||
return self.child.elements
|
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):
|
def parse_args(self, args, index=0):
|
||||||
return self.child.parse_args(args, index)
|
return self.child.parse_args(args, index)
|
||||||
|
|
||||||
def parse_kwargs(self, kwargs, name, available_kwargs):
|
def parse_kwargs(self, kwargs, name, available_kwargs):
|
||||||
if self.accepts_multiple:
|
if self.accepts_multiple:
|
||||||
if name not in kwargs:
|
if name not in kwargs:
|
||||||
return {}, kwargs
|
return {}
|
||||||
|
|
||||||
available_kwargs.remove(name)
|
available_kwargs.remove(name)
|
||||||
item_kwargs = kwargs[name]
|
item_kwargs = kwargs[name]
|
||||||
|
@ -536,6 +679,11 @@ class Group(Indicator):
|
||||||
subresult = self.child.parse_kwargs(
|
subresult = self.child.parse_kwargs(
|
||||||
sub_kwargs, sub_name, available_sub_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:
|
if subresult:
|
||||||
result.append(subresult)
|
result.append(subresult)
|
||||||
if result:
|
if result:
|
||||||
|
@ -545,6 +693,19 @@ class Group(Indicator):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
|
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 = []
|
result = []
|
||||||
|
|
||||||
for _unused in max_occurs_iter(self.max_occurs):
|
for _unused in max_occurs_iter(self.max_occurs):
|
||||||
|
@ -552,16 +713,29 @@ class Group(Indicator):
|
||||||
self.child.parse_xmlelements(
|
self.child.parse_xmlelements(
|
||||||
xmlelements, schema, name, context=context)
|
xmlelements, schema, name, context=context)
|
||||||
)
|
)
|
||||||
|
if not xmlelements:
|
||||||
|
break
|
||||||
if not self.accepts_multiple and result:
|
if not self.accepts_multiple and result:
|
||||||
return result[0]
|
return result[0]
|
||||||
return {name: result}
|
return {name: result}
|
||||||
|
|
||||||
def render(self, *args, **kwargs):
|
def render(self, parent, value, render_path):
|
||||||
return self.child.render(*args, **kwargs)
|
if not isinstance(value, list):
|
||||||
|
values = [value]
|
||||||
|
else:
|
||||||
|
values = value
|
||||||
|
|
||||||
|
for value in values:
|
||||||
|
self.child.render(parent, value, render_path)
|
||||||
|
|
||||||
def resolve(self):
|
def resolve(self):
|
||||||
self.child = self.child.resolve()
|
self.child = self.child.resolve()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=True):
|
||||||
return self.child.signature(depth)
|
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)
|
||||||
|
|
|
@ -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']
|
__all__ = ['RefElement', 'RefAttribute', 'RefAttributeGroup', 'RefGroup']
|
||||||
|
|
||||||
|
|
||||||
class RefElement(object):
|
class RefElement(object):
|
||||||
|
|
||||||
def __init__(self, tag, ref, schema, is_qualified=False,
|
def __init__(self, tag, ref, schema, is_qualified=False,
|
||||||
min_occurs=1, max_occurs=1):
|
min_occurs=1, max_occurs=1):
|
||||||
self._ref = ref
|
self._ref = ref
|
||||||
|
@ -37,4 +44,7 @@ class RefAttributeGroup(RefElement):
|
||||||
|
|
||||||
class RefGroup(RefElement):
|
class RefGroup(RefElement):
|
||||||
def resolve(self):
|
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
|
||||||
|
|
|
@ -3,8 +3,7 @@ from collections import OrderedDict
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from zeep import exceptions
|
from zeep import exceptions, ns
|
||||||
from zeep.xsd import const
|
|
||||||
from zeep.xsd.elements import builtins as xsd_builtins_elements
|
from zeep.xsd.elements import builtins as xsd_builtins_elements
|
||||||
from zeep.xsd.types import builtins as xsd_builtins_types
|
from zeep.xsd.types import builtins as xsd_builtins_types
|
||||||
from zeep.xsd.visitor import SchemaVisitor
|
from zeep.xsd.visitor import SchemaVisitor
|
||||||
|
@ -15,13 +14,24 @@ logger = logging.getLogger(__name__)
|
||||||
class Schema(object):
|
class Schema(object):
|
||||||
"""A schema is a collection of schema documents."""
|
"""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._transport = transport
|
||||||
|
|
||||||
self._documents = OrderedDict()
|
self._documents = OrderedDict()
|
||||||
self._prefix_map_auto = {}
|
self._prefix_map_auto = {}
|
||||||
self._prefix_map_custom = {}
|
self._prefix_map_custom = {}
|
||||||
|
|
||||||
|
self._load_default_documents()
|
||||||
|
|
||||||
if not isinstance(node, list):
|
if not isinstance(node, list):
|
||||||
nodes = [node] if node is not None else []
|
nodes = [node] if node is not None else []
|
||||||
else:
|
else:
|
||||||
|
@ -44,6 +54,12 @@ class Schema(object):
|
||||||
})
|
})
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root_document(self):
|
||||||
|
return next(
|
||||||
|
(doc for doc in self.documents if not doc._is_internal),
|
||||||
|
None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_empty(self):
|
def is_empty(self):
|
||||||
"""Boolean to indicate if this schema contains any types or elements"""
|
"""Boolean to indicate if this schema contains any types or elements"""
|
||||||
|
@ -55,25 +71,38 @@ class Schema(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def elements(self):
|
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 document in self.documents:
|
||||||
for element in document._elements.values():
|
for element in document._elements.values():
|
||||||
yield element
|
if element.qname not in seen:
|
||||||
|
yield element
|
||||||
|
seen.add(element.qname)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def types(self):
|
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 document in self.documents:
|
||||||
for type_ in document._types.values():
|
for type_ in document._types.values():
|
||||||
yield type_
|
if type_.qname not in seen:
|
||||||
|
yield type_
|
||||||
|
seen.add(type_.qname)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self._documents:
|
main_doc = self.root_document
|
||||||
main_doc = next(self.documents)
|
if main_doc:
|
||||||
location = main_doc._location
|
return '<Schema(location=%r, tns=%r)>' % (
|
||||||
else:
|
main_doc._location, main_doc._target_namespace)
|
||||||
location = '<none>'
|
return '<Schema()>'
|
||||||
return '<Schema(location=%r)>' % location
|
|
||||||
|
|
||||||
def add_documents(self, schema_nodes, location):
|
def add_documents(self, schema_nodes, location):
|
||||||
documents = []
|
documents = []
|
||||||
|
@ -87,49 +116,51 @@ class Schema(object):
|
||||||
self._prefix_map_auto = self._create_prefix_map()
|
self._prefix_map_auto = self._create_prefix_map()
|
||||||
|
|
||||||
def get_element(self, qname):
|
def get_element(self, qname):
|
||||||
"""Return a global xsd.Element object with the given qname"""
|
"""Return a global xsd.Element object with the given qname
|
||||||
|
|
||||||
|
:rtype: zeep.xsd.Group
|
||||||
|
|
||||||
|
"""
|
||||||
qname = self._create_qname(qname)
|
qname = self._create_qname(qname)
|
||||||
if qname.text in xsd_builtins_elements.default_elements:
|
|
||||||
return xsd_builtins_elements.default_elements[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)
|
|
||||||
|
|
||||||
return self._get_instance(qname, 'get_element', 'element')
|
return self._get_instance(qname, 'get_element', 'element')
|
||||||
|
|
||||||
def get_type(self, qname, fail_silently=False):
|
def get_type(self, qname, fail_silently=False):
|
||||||
"""Return a global xsd.Type object with the given qname"""
|
"""Return a global xsd.Type object with the given qname
|
||||||
|
|
||||||
|
:rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
|
||||||
|
|
||||||
|
"""
|
||||||
qname = self._create_qname(qname)
|
qname = self._create_qname(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)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self._get_instance(qname, 'get_type', 'type')
|
return self._get_instance(qname, 'get_type', 'type')
|
||||||
except exceptions.NamespaceError as exc:
|
except exceptions.NamespaceError as exc:
|
||||||
if fail_silently:
|
if fail_silently:
|
||||||
logger.info(str(exc))
|
logger.debug(str(exc))
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def get_group(self, qname):
|
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')
|
return self._get_instance(qname, 'get_group', 'group')
|
||||||
|
|
||||||
def get_attribute(self, qname):
|
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')
|
return self._get_instance(qname, 'get_attribute', 'attribute')
|
||||||
|
|
||||||
def get_attribute_group(self, qname):
|
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')
|
return self._get_instance(qname, 'get_attribute_group', 'attributeGroup')
|
||||||
|
|
||||||
def set_ns_prefix(self, prefix, namespace):
|
def set_ns_prefix(self, prefix, namespace):
|
||||||
|
@ -144,6 +175,18 @@ class Schema(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("No such prefix %r" % prefix)
|
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):
|
def create_new_document(self, node, url, base_url=None):
|
||||||
namespace = node.get('targetNamespace') if node is not None else None
|
namespace = node.get('targetNamespace') if node is not None else None
|
||||||
if base_url is None:
|
if base_url is None:
|
||||||
|
@ -160,6 +203,21 @@ class Schema(object):
|
||||||
self._add_schema_document(document)
|
self._add_schema_document(document)
|
||||||
self._prefix_map_auto = self._create_prefix_map()
|
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):
|
def _get_instance(self, qname, method_name, name):
|
||||||
"""Return an object from one of the SchemaDocument's"""
|
"""Return an object from one of the SchemaDocument's"""
|
||||||
qname = self._create_qname(qname)
|
qname = self._create_qname(qname)
|
||||||
|
@ -185,6 +243,8 @@ class Schema(object):
|
||||||
|
|
||||||
This also expands the shorthand notation.
|
This also expands the shorthand notation.
|
||||||
|
|
||||||
|
:rtype: lxml.etree.QNaame
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(name, etree.QName):
|
if isinstance(name, etree.QName):
|
||||||
return name
|
return name
|
||||||
|
@ -205,26 +265,45 @@ class Schema(object):
|
||||||
prefix_map = {
|
prefix_map = {
|
||||||
'xsd': 'http://www.w3.org/2001/XMLSchema',
|
'xsd': 'http://www.w3.org/2001/XMLSchema',
|
||||||
}
|
}
|
||||||
for i, namespace in enumerate(self._documents.keys()):
|
i = 0
|
||||||
if namespace is None:
|
for namespace in self._documents.keys():
|
||||||
|
if namespace is None or namespace in prefix_map.values():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
prefix_map['ns%d' % i] = namespace
|
prefix_map['ns%d' % i] = namespace
|
||||||
|
i += 1
|
||||||
return prefix_map
|
return prefix_map
|
||||||
|
|
||||||
def _has_schema_document(self, namespace):
|
def _has_schema_document(self, namespace):
|
||||||
|
"""Return a boolean if there is a SchemaDocumnet for the namespace.
|
||||||
|
|
||||||
|
:rtype: boolean
|
||||||
|
|
||||||
|
"""
|
||||||
return namespace in self._documents
|
return namespace in self._documents
|
||||||
|
|
||||||
def _add_schema_document(self, document):
|
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 = self._documents.setdefault(document.namespace, [])
|
||||||
documents.append(document)
|
documents.append(document)
|
||||||
|
|
||||||
def _get_schema_document(self, namespace, location):
|
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, []):
|
for document in self._documents.get(namespace, []):
|
||||||
if document._location == location:
|
if document._location == location:
|
||||||
return document
|
return document
|
||||||
|
|
||||||
def _get_schema_documents(self, namespace, fail_silently=False):
|
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 namespace not in self._documents:
|
||||||
if fail_silently:
|
if fail_silently:
|
||||||
return []
|
return []
|
||||||
|
@ -241,7 +320,7 @@ class SchemaDocument(object):
|
||||||
self._base_url = base_url or location
|
self._base_url = base_url or location
|
||||||
self._location = location
|
self._location = location
|
||||||
self._target_namespace = namespace
|
self._target_namespace = namespace
|
||||||
self._elm_instances = []
|
self._is_internal = False
|
||||||
|
|
||||||
self._attribute_groups = {}
|
self._attribute_groups = {}
|
||||||
self._attributes = {}
|
self._attributes = {}
|
||||||
|
@ -283,7 +362,7 @@ class SchemaDocument(object):
|
||||||
visitor.visit_schema(node)
|
visitor.visit_schema(node)
|
||||||
|
|
||||||
def resolve(self):
|
def resolve(self):
|
||||||
logger.info("Resolving in schema %s", self)
|
logger.debug("Resolving in schema %s", self)
|
||||||
|
|
||||||
if self._resolved:
|
if self._resolved:
|
||||||
return
|
return
|
||||||
|
@ -294,10 +373,23 @@ class SchemaDocument(object):
|
||||||
schema.resolve()
|
schema.resolve()
|
||||||
|
|
||||||
def _resolve_dict(val):
|
def _resolve_dict(val):
|
||||||
for key, obj in val.items():
|
try:
|
||||||
new = obj.resolve()
|
for key, obj in val.items():
|
||||||
assert new is not None, "resolve() should return an object"
|
new = obj.resolve()
|
||||||
val[key] = new
|
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._attribute_groups)
|
||||||
_resolve_dict(self._attributes)
|
_resolve_dict(self._attributes)
|
||||||
|
@ -305,10 +397,6 @@ class SchemaDocument(object):
|
||||||
_resolve_dict(self._groups)
|
_resolve_dict(self._groups)
|
||||||
_resolve_dict(self._types)
|
_resolve_dict(self._types)
|
||||||
|
|
||||||
for element in self._elm_instances:
|
|
||||||
element.resolve()
|
|
||||||
self._elm_instances = []
|
|
||||||
|
|
||||||
def register_import(self, namespace, schema):
|
def register_import(self, namespace, schema):
|
||||||
schemas = self._imports.setdefault(namespace, [])
|
schemas = self._imports.setdefault(namespace, [])
|
||||||
schemas.append(schema)
|
schemas.append(schema)
|
||||||
|
@ -350,23 +438,43 @@ class SchemaDocument(object):
|
||||||
self._attribute_groups[name] = value
|
self._attribute_groups[name] = value
|
||||||
|
|
||||||
def get_type(self, qname):
|
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')
|
return self._get_instance(qname, self._types, 'type')
|
||||||
|
|
||||||
def get_element(self, qname):
|
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')
|
return self._get_instance(qname, self._elements, 'element')
|
||||||
|
|
||||||
def get_group(self, qname):
|
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')
|
return self._get_instance(qname, self._groups, 'group')
|
||||||
|
|
||||||
def get_attribute(self, qname):
|
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')
|
return self._get_instance(qname, self._attributes, 'attribute')
|
||||||
|
|
||||||
def get_attribute_group(self, qname):
|
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')
|
return self._get_instance(qname, self._attribute_groups, 'attributeGroup')
|
||||||
|
|
||||||
def _get_instance(self, qname, items, item_name):
|
def _get_instance(self, qname, items, item_name):
|
||||||
|
@ -377,10 +485,13 @@ class SchemaDocument(object):
|
||||||
raise exceptions.LookupError((
|
raise exceptions.LookupError((
|
||||||
"No %(item_name)s '%(localname)s' in namespace %(namespace)s. " +
|
"No %(item_name)s '%(localname)s' in namespace %(namespace)s. " +
|
||||||
"Available %(item_name_plural)s are: %(known_items)s"
|
"Available %(item_name_plural)s are: %(known_items)s"
|
||||||
) % {
|
) % {
|
||||||
'item_name': item_name,
|
'item_name': item_name,
|
||||||
'item_name_plural': item_name + 's',
|
'item_name_plural': item_name + 's',
|
||||||
'localname': qname.localname,
|
'localname': qname.localname,
|
||||||
'namespace': qname.namespace,
|
'namespace': qname.namespace,
|
||||||
'known_items': known_items or ' - '
|
'known_items': known_items or ' - '
|
||||||
})
|
},
|
||||||
|
qname=qname,
|
||||||
|
item_name=item_name,
|
||||||
|
location=self._location)
|
||||||
|
|
|
@ -12,6 +12,11 @@ __all__ = ['AnyType']
|
||||||
|
|
||||||
class AnyType(Type):
|
class AnyType(Type):
|
||||||
_default_qname = xsd_ns('anyType')
|
_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):
|
def render(self, parent, value, xsd_type=None, render_path=None):
|
||||||
if isinstance(value, AnyObject):
|
if isinstance(value, AnyObject):
|
||||||
|
@ -27,7 +32,22 @@ class AnyType(Type):
|
||||||
parent.text = self.xmlvalue(value)
|
parent.text = self.xmlvalue(value)
|
||||||
|
|
||||||
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
|
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_type = qname_attr(xmlelement, xsi_ns('type'))
|
||||||
xsi_nil = xmlelement.get(xsi_ns('nil'))
|
xsi_nil = xmlelement.get(xsi_ns('nil'))
|
||||||
children = list(xmlelement.getchildren())
|
children = list(xmlelement.getchildren())
|
||||||
|
@ -42,8 +62,14 @@ class AnyType(Type):
|
||||||
xsd_type = schema.get_type(xsi_type, fail_silently=True)
|
xsd_type = schema.get_type(xsi_type, fail_silently=True)
|
||||||
|
|
||||||
# If we were unable to resolve a type for the xsi:type (due to
|
# 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:
|
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
|
return children
|
||||||
|
|
||||||
# If the xsd_type is xsd:anyType then we will recurs so ignore
|
# 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):
|
def pythonvalue(self, value, schema=None):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def signature(self, schema=None, standalone=True):
|
||||||
|
return 'xsd:anyType'
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
from zeep.xsd.utils import create_prefixed_name
|
||||||
|
|
||||||
|
__all__ = ['Type']
|
||||||
|
|
||||||
|
|
||||||
class Type(object):
|
class Type(object):
|
||||||
|
|
||||||
def __init__(self, qname=None, is_global=False):
|
def __init__(self, qname=None, is_global=False):
|
||||||
|
@ -6,9 +11,16 @@ class Type(object):
|
||||||
self._resolved = False
|
self._resolved = False
|
||||||
self.is_global = is_global
|
self.is_global = is_global
|
||||||
|
|
||||||
|
def get_prefixed_name(self, schema):
|
||||||
|
return create_prefixed_name(self.qname, schema)
|
||||||
|
|
||||||
def accept(self, value):
|
def accept(self, value):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accepted_types(self):
|
||||||
|
return tuple()
|
||||||
|
|
||||||
def validate(self, value, required=False):
|
def validate(self, value, required=False):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -23,7 +35,7 @@ class Type(object):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
|
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
|
||||||
context=None):
|
context=None, schema_type=None):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
'%s.parse_xmlelement() is not implemented' % self.__class__.__name__)
|
'%s.parse_xmlelement() is not implemented' % self.__class__.__name__)
|
||||||
|
|
||||||
|
@ -51,61 +63,5 @@ class Type(object):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def signature(cls, depth=()):
|
def signature(cls, schema=None, standalone=True):
|
||||||
return ''
|
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)
|
|
||||||
|
|
|
@ -17,6 +17,11 @@ class ParseError(ValueError):
|
||||||
pass
|
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 check_no_collection(func):
|
||||||
def _wrapper(self, value):
|
def _wrapper(self, value):
|
||||||
if isinstance(value, (list, dict, set)):
|
if isinstance(value, (list, dict, set)):
|
||||||
|
@ -30,7 +35,7 @@ def check_no_collection(func):
|
||||||
|
|
||||||
##
|
##
|
||||||
# Primitive types
|
# Primitive types
|
||||||
class String(AnySimpleType):
|
class String(BuiltinType, AnySimpleType):
|
||||||
_default_qname = xsd_ns('string')
|
_default_qname = xsd_ns('string')
|
||||||
accepted_types = six.string_types
|
accepted_types = six.string_types
|
||||||
|
|
||||||
|
@ -44,13 +49,13 @@ class String(AnySimpleType):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class Boolean(AnySimpleType):
|
class Boolean(BuiltinType, AnySimpleType):
|
||||||
_default_qname = xsd_ns('boolean')
|
_default_qname = xsd_ns('boolean')
|
||||||
accepted_types = (bool,)
|
accepted_types = (bool,)
|
||||||
|
|
||||||
@check_no_collection
|
@check_no_collection
|
||||||
def xmlvalue(self, value):
|
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):
|
def pythonvalue(self, value):
|
||||||
"""Return True if the 'true' or '1'. 'false' and '0' are legal false
|
"""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')
|
return value in ('true', '1')
|
||||||
|
|
||||||
|
|
||||||
class Decimal(AnySimpleType):
|
class Decimal(BuiltinType, AnySimpleType):
|
||||||
_default_qname = xsd_ns('decimal')
|
_default_qname = xsd_ns('decimal')
|
||||||
accepted_types = (_Decimal, float) + six.string_types
|
accepted_types = (_Decimal, float) + six.string_types
|
||||||
|
|
||||||
|
@ -72,7 +77,7 @@ class Decimal(AnySimpleType):
|
||||||
return _Decimal(value)
|
return _Decimal(value)
|
||||||
|
|
||||||
|
|
||||||
class Float(AnySimpleType):
|
class Float(BuiltinType, AnySimpleType):
|
||||||
_default_qname = xsd_ns('float')
|
_default_qname = xsd_ns('float')
|
||||||
accepted_types = (float, _Decimal) + six.string_types
|
accepted_types = (float, _Decimal) + six.string_types
|
||||||
|
|
||||||
|
@ -83,7 +88,7 @@ class Float(AnySimpleType):
|
||||||
return float(value)
|
return float(value)
|
||||||
|
|
||||||
|
|
||||||
class Double(AnySimpleType):
|
class Double(BuiltinType, AnySimpleType):
|
||||||
_default_qname = xsd_ns('double')
|
_default_qname = xsd_ns('double')
|
||||||
accepted_types = (_Decimal, float) + six.string_types
|
accepted_types = (_Decimal, float) + six.string_types
|
||||||
|
|
||||||
|
@ -95,7 +100,7 @@ class Double(AnySimpleType):
|
||||||
return float(value)
|
return float(value)
|
||||||
|
|
||||||
|
|
||||||
class Duration(AnySimpleType):
|
class Duration(BuiltinType, AnySimpleType):
|
||||||
_default_qname = xsd_ns('duration')
|
_default_qname = xsd_ns('duration')
|
||||||
accepted_types = (isodate.duration.Duration,) + six.string_types
|
accepted_types = (isodate.duration.Duration,) + six.string_types
|
||||||
|
|
||||||
|
@ -107,7 +112,7 @@ class Duration(AnySimpleType):
|
||||||
return isodate.parse_duration(value)
|
return isodate.parse_duration(value)
|
||||||
|
|
||||||
|
|
||||||
class DateTime(AnySimpleType):
|
class DateTime(BuiltinType, AnySimpleType):
|
||||||
_default_qname = xsd_ns('dateTime')
|
_default_qname = xsd_ns('dateTime')
|
||||||
accepted_types = (datetime.datetime,) + six.string_types
|
accepted_types = (datetime.datetime,) + six.string_types
|
||||||
|
|
||||||
|
@ -131,7 +136,7 @@ class DateTime(AnySimpleType):
|
||||||
return isodate.parse_datetime(value)
|
return isodate.parse_datetime(value)
|
||||||
|
|
||||||
|
|
||||||
class Time(AnySimpleType):
|
class Time(BuiltinType, AnySimpleType):
|
||||||
_default_qname = xsd_ns('time')
|
_default_qname = xsd_ns('time')
|
||||||
accepted_types = (datetime.time,) + six.string_types
|
accepted_types = (datetime.time,) + six.string_types
|
||||||
|
|
||||||
|
@ -145,7 +150,7 @@ class Time(AnySimpleType):
|
||||||
return isodate.parse_time(value)
|
return isodate.parse_time(value)
|
||||||
|
|
||||||
|
|
||||||
class Date(AnySimpleType):
|
class Date(BuiltinType, AnySimpleType):
|
||||||
_default_qname = xsd_ns('date')
|
_default_qname = xsd_ns('date')
|
||||||
accepted_types = (datetime.date,) + six.string_types
|
accepted_types = (datetime.date,) + six.string_types
|
||||||
|
|
||||||
|
@ -159,7 +164,7 @@ class Date(AnySimpleType):
|
||||||
return isodate.parse_date(value)
|
return isodate.parse_date(value)
|
||||||
|
|
||||||
|
|
||||||
class gYearMonth(AnySimpleType):
|
class gYearMonth(BuiltinType, AnySimpleType):
|
||||||
"""gYearMonth represents a specific gregorian month in a specific gregorian
|
"""gYearMonth represents a specific gregorian month in a specific gregorian
|
||||||
year.
|
year.
|
||||||
|
|
||||||
|
@ -186,7 +191,7 @@ class gYearMonth(AnySimpleType):
|
||||||
_parse_timezone(group['timezone']))
|
_parse_timezone(group['timezone']))
|
||||||
|
|
||||||
|
|
||||||
class gYear(AnySimpleType):
|
class gYear(BuiltinType, AnySimpleType):
|
||||||
"""gYear represents a gregorian calendar year.
|
"""gYear represents a gregorian calendar year.
|
||||||
|
|
||||||
Lexical representation: CCYY
|
Lexical representation: CCYY
|
||||||
|
@ -209,7 +214,7 @@ class gYear(AnySimpleType):
|
||||||
return (int(group['year']), _parse_timezone(group['timezone']))
|
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
|
"""gMonthDay is a gregorian date that recurs, specifically a day of the
|
||||||
year such as the third of May.
|
year such as the third of May.
|
||||||
|
|
||||||
|
@ -237,7 +242,7 @@ class gMonthDay(AnySimpleType):
|
||||||
_parse_timezone(group['timezone']))
|
_parse_timezone(group['timezone']))
|
||||||
|
|
||||||
|
|
||||||
class gDay(AnySimpleType):
|
class gDay(BuiltinType, AnySimpleType):
|
||||||
"""gDay is a gregorian day that recurs, specifically a day of the month
|
"""gDay is a gregorian day that recurs, specifically a day of the month
|
||||||
such as the 5th 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']))
|
return (int(group['day']), _parse_timezone(group['timezone']))
|
||||||
|
|
||||||
|
|
||||||
class gMonth(AnySimpleType):
|
class gMonth(BuiltinType, AnySimpleType):
|
||||||
"""gMonth is a gregorian month that recurs every year.
|
"""gMonth is a gregorian month that recurs every year.
|
||||||
|
|
||||||
Lexical representation: --MM
|
Lexical representation: --MM
|
||||||
|
@ -284,7 +289,7 @@ class gMonth(AnySimpleType):
|
||||||
return (int(group['month']), _parse_timezone(group['timezone']))
|
return (int(group['month']), _parse_timezone(group['timezone']))
|
||||||
|
|
||||||
|
|
||||||
class HexBinary(AnySimpleType):
|
class HexBinary(BuiltinType, AnySimpleType):
|
||||||
accepted_types = six.string_types
|
accepted_types = six.string_types
|
||||||
_default_qname = xsd_ns('hexBinary')
|
_default_qname = xsd_ns('hexBinary')
|
||||||
|
|
||||||
|
@ -296,7 +301,7 @@ class HexBinary(AnySimpleType):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class Base64Binary(AnySimpleType):
|
class Base64Binary(BuiltinType, AnySimpleType):
|
||||||
accepted_types = six.string_types
|
accepted_types = six.string_types
|
||||||
_default_qname = xsd_ns('base64Binary')
|
_default_qname = xsd_ns('base64Binary')
|
||||||
|
|
||||||
|
@ -308,7 +313,7 @@ class Base64Binary(AnySimpleType):
|
||||||
return base64.b64decode(value)
|
return base64.b64decode(value)
|
||||||
|
|
||||||
|
|
||||||
class AnyURI(AnySimpleType):
|
class AnyURI(BuiltinType, AnySimpleType):
|
||||||
accepted_types = six.string_types
|
accepted_types = six.string_types
|
||||||
_default_qname = xsd_ns('anyURI')
|
_default_qname = xsd_ns('anyURI')
|
||||||
|
|
||||||
|
@ -320,7 +325,7 @@ class AnyURI(AnySimpleType):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class QName(AnySimpleType):
|
class QName(BuiltinType, AnySimpleType):
|
||||||
accepted_types = six.string_types
|
accepted_types = six.string_types
|
||||||
_default_qname = xsd_ns('QName')
|
_default_qname = xsd_ns('QName')
|
||||||
|
|
||||||
|
@ -332,7 +337,7 @@ class QName(AnySimpleType):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class Notation(AnySimpleType):
|
class Notation(BuiltinType, AnySimpleType):
|
||||||
accepted_types = six.string_types
|
accepted_types = six.string_types
|
||||||
_default_qname = xsd_ns('NOTATION')
|
_default_qname = xsd_ns('NOTATION')
|
||||||
|
|
||||||
|
@ -390,6 +395,7 @@ class Entities(Entity):
|
||||||
|
|
||||||
class Integer(Decimal):
|
class Integer(Decimal):
|
||||||
_default_qname = xsd_ns('integer')
|
_default_qname = xsd_ns('integer')
|
||||||
|
accepted_types = (int, float) + six.string_types
|
||||||
|
|
||||||
def xmlvalue(self, value):
|
def xmlvalue(self, value):
|
||||||
return str(value)
|
return str(value)
|
||||||
|
@ -484,58 +490,60 @@ def _unparse_timezone(tzinfo):
|
||||||
return '-%02d:%02d' % (abs(hours), minutes)
|
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 = {
|
default_types = {
|
||||||
cls._default_qname: cls() for cls in [
|
cls._default_qname: cls(is_global=True) for cls in _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,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,8 +32,8 @@ class ListType(AnySimpleType):
|
||||||
item_type = self.item_type
|
item_type = self.item_type
|
||||||
return [item_type.pythonvalue(v) for v in value.split()]
|
return [item_type.pythonvalue(v) for v in value.split()]
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=True):
|
||||||
return self.item_type.signature(depth) + '[]'
|
return self.item_type.signature(schema) + '[]'
|
||||||
|
|
||||||
|
|
||||||
class UnionType(AnySimpleType):
|
class UnionType(AnySimpleType):
|
||||||
|
@ -52,11 +52,11 @@ class UnionType(AnySimpleType):
|
||||||
self.item_class = base_class
|
self.item_class = base_class
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=True):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
|
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
|
||||||
context=None):
|
context=None, schema_type=None):
|
||||||
if self.item_class:
|
if self.item_class:
|
||||||
return self.item_class().parse_xmlelement(
|
return self.item_class().parse_xmlelement(
|
||||||
xmlelement, schema, allow_none, context)
|
xmlelement, schema, allow_none, context)
|
||||||
|
|
|
@ -6,14 +6,14 @@ from itertools import chain
|
||||||
from cached_property import threaded_cached_property
|
from cached_property import threaded_cached_property
|
||||||
|
|
||||||
from zeep.exceptions import UnexpectedElementError, XMLParseError
|
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 (
|
from zeep.xsd.elements import (
|
||||||
Any, AnyAttribute, AttributeGroup, Choice, Element, Group, Sequence)
|
Any, AnyAttribute, AttributeGroup, Choice, Element, Group, Sequence)
|
||||||
from zeep.xsd.elements.indicators import OrderIndicator
|
from zeep.xsd.elements.indicators import OrderIndicator
|
||||||
from zeep.xsd.types.any import AnyType
|
from zeep.xsd.types.any import AnyType
|
||||||
from zeep.xsd.types.simple import AnySimpleType
|
from zeep.xsd.types.simple import AnySimpleType
|
||||||
from zeep.xsd.utils import NamePrefixGenerator
|
from zeep.xsd.utils import NamePrefixGenerator
|
||||||
from zeep.xsd.valueobjects import CompoundValue
|
from zeep.xsd.valueobjects import CompoundValue, ArrayValue
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -33,14 +33,24 @@ class ComplexType(AnyType):
|
||||||
self._attributes = attributes or []
|
self._attributes = attributes or []
|
||||||
self._restriction = restriction
|
self._restriction = restriction
|
||||||
self._extension = extension
|
self._extension = extension
|
||||||
|
self._extension_types = tuple()
|
||||||
super(ComplexType, self).__init__(qname=qname, is_global=is_global)
|
super(ComplexType, self).__init__(qname=qname, is_global=is_global)
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
|
if self._array_type:
|
||||||
|
return self._array_class(*args, **kwargs)
|
||||||
return self._value_class(*args, **kwargs)
|
return self._value_class(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def accepted_types(self):
|
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
|
@threaded_cached_property
|
||||||
def _value_class(self):
|
def _value_class(self):
|
||||||
|
@ -94,25 +104,43 @@ class ComplexType(AnyType):
|
||||||
generator = NamePrefixGenerator()
|
generator = NamePrefixGenerator()
|
||||||
|
|
||||||
# Handle wsdl:arrayType objects
|
# Handle wsdl:arrayType objects
|
||||||
attrs = {attr.qname.text: attr for attr in self._attributes if attr.qname}
|
if self._array_type:
|
||||||
array_type = attrs.get('{http://schemas.xmlsoap.org/soap/encoding/}arrayType')
|
|
||||||
if array_type:
|
|
||||||
name = generator.get_name()
|
name = generator.get_name()
|
||||||
if isinstance(self._element, Group):
|
if isinstance(self._element, Group):
|
||||||
return [(name, Sequence([
|
result = [(name, Sequence([
|
||||||
Any(max_occurs='unbounded', restrict=array_type.array_type)
|
Any(max_occurs='unbounded', restrict=self._array_type.array_type)
|
||||||
]))]
|
]))]
|
||||||
else:
|
else:
|
||||||
return [(name, self._element)]
|
result = [(name, self._element)]
|
||||||
|
else:
|
||||||
# _element is one of All, Choice, Group, Sequence
|
# _element is one of All, Choice, Group, Sequence
|
||||||
if self._element:
|
if self._element:
|
||||||
result.append((generator.get_name(), self._element))
|
result.append((generator.get_name(), self._element))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def parse_xmlelement(self, xmlelement, schema, allow_none=True,
|
@property
|
||||||
context=None):
|
def _array_type(self):
|
||||||
"""Consume matching xmlelements and call parse() on each"""
|
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 this is an empty complexType (<xsd:complexType name="x"/>)
|
||||||
if not self.attributes and not self.elements:
|
if not self.attributes and not self.elements:
|
||||||
return None
|
return None
|
||||||
|
@ -134,6 +162,7 @@ class ComplexType(AnyType):
|
||||||
|
|
||||||
# Parse elements. These are always indicator elements (all, choice,
|
# Parse elements. These are always indicator elements (all, choice,
|
||||||
# group, sequence)
|
# group, sequence)
|
||||||
|
assert len(self.elements_nested) < 2
|
||||||
for name, element in self.elements_nested:
|
for name, element in self.elements_nested:
|
||||||
try:
|
try:
|
||||||
result = element.parse_xmlelements(
|
result = element.parse_xmlelements(
|
||||||
|
@ -145,7 +174,10 @@ class ComplexType(AnyType):
|
||||||
|
|
||||||
# Check if all children are consumed (parsed)
|
# Check if all children are consumed (parsed)
|
||||||
if elements:
|
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
|
# Parse attributes
|
||||||
if attributes:
|
if attributes:
|
||||||
|
@ -158,12 +190,21 @@ class ComplexType(AnyType):
|
||||||
else:
|
else:
|
||||||
init_kwargs[name] = attribute.parse(attributes)
|
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):
|
def render(self, parent, value, xsd_type=None, render_path=None):
|
||||||
"""Serialize the given value lxml.Element subelements on the parent
|
"""Serialize the given value lxml.Element subelements on the parent
|
||||||
element.
|
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:
|
if not render_path:
|
||||||
render_path = [self.name]
|
render_path = [self.name]
|
||||||
|
@ -171,12 +212,24 @@ class ComplexType(AnyType):
|
||||||
if not self.elements_nested and not self.attributes:
|
if not self.elements_nested and not self.attributes:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if isinstance(value, ArrayValue):
|
||||||
|
value = value.as_value_object()
|
||||||
|
|
||||||
# Render attributes
|
# Render attributes
|
||||||
for name, attribute in self.attributes:
|
for name, attribute in self.attributes:
|
||||||
attr_value = value[name] if name in value else NotSet
|
attr_value = value[name] if name in value else NotSet
|
||||||
child_path = render_path + [name]
|
child_path = render_path + [name]
|
||||||
attribute.render(parent, attr_value, child_path)
|
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
|
# Render sub elements
|
||||||
for name, element in self.elements_nested:
|
for name, element in self.elements_nested:
|
||||||
if isinstance(element, Element) or element.accepts_multiple:
|
if isinstance(element, Element) or element.accepts_multiple:
|
||||||
|
@ -186,6 +239,7 @@ class ComplexType(AnyType):
|
||||||
element_value = value
|
element_value = value
|
||||||
child_path = list(render_path)
|
child_path = list(render_path)
|
||||||
|
|
||||||
|
# We want to explicitly skip this sub-element
|
||||||
if element_value is SkipValue:
|
if element_value is SkipValue:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -201,6 +255,19 @@ class ComplexType(AnyType):
|
||||||
parent.set(xsi_ns('type'), xsd_type.qname)
|
parent.set(xsi_ns('type'), xsd_type.qname)
|
||||||
|
|
||||||
def parse_kwargs(self, kwargs, name, available_kwargs):
|
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
|
value = None
|
||||||
name = name or self.name
|
name = name or self.name
|
||||||
|
|
||||||
|
@ -213,28 +280,27 @@ class ComplexType(AnyType):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _create_object(self, value, name):
|
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:
|
if value is None:
|
||||||
return 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]
|
return [self._create_object(val, name) for val in value]
|
||||||
|
|
||||||
if isinstance(value, CompoundValue):
|
if isinstance(value, CompoundValue) or value is SkipValue:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
return self(**value)
|
return self(**value)
|
||||||
|
|
||||||
# Check if the valueclass only expects one value, in that case
|
# Try to automatically create an object. This might fail if there
|
||||||
# we can try to automatically create an object for it.
|
# are multiple required arguments.
|
||||||
if len(self.attributes) + len(self.elements) == 1:
|
return self(value)
|
||||||
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)))
|
|
||||||
|
|
||||||
def resolve(self):
|
def resolve(self):
|
||||||
"""Resolve all sub elements and types"""
|
"""Resolve all sub elements and types"""
|
||||||
|
@ -242,9 +308,6 @@ class ComplexType(AnyType):
|
||||||
return self._resolved
|
return self._resolved
|
||||||
self._resolved = self
|
self._resolved = self
|
||||||
|
|
||||||
if self._element:
|
|
||||||
self._element = self._element.resolve()
|
|
||||||
|
|
||||||
resolved = []
|
resolved = []
|
||||||
for attribute in self._attributes:
|
for attribute in self._attributes:
|
||||||
value = attribute.resolve()
|
value = attribute.resolve()
|
||||||
|
@ -258,18 +321,17 @@ class ComplexType(AnyType):
|
||||||
if self._extension:
|
if self._extension:
|
||||||
self._extension = self._extension.resolve()
|
self._extension = self._extension.resolve()
|
||||||
self._resolved = self.extend(self._extension)
|
self._resolved = self.extend(self._extension)
|
||||||
return self._resolved
|
|
||||||
|
|
||||||
elif self._restriction:
|
elif self._restriction:
|
||||||
self._restriction = self._restriction.resolve()
|
self._restriction = self._restriction.resolve()
|
||||||
self._resolved = self.restrict(self._restriction)
|
self._resolved = self.restrict(self._restriction)
|
||||||
return self._resolved
|
|
||||||
|
|
||||||
else:
|
if self._element:
|
||||||
return self._resolved
|
self._element = self._element.resolve()
|
||||||
|
|
||||||
|
return self._resolved
|
||||||
|
|
||||||
def extend(self, base):
|
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.
|
extending the given base type.
|
||||||
|
|
||||||
Used for handling xsd:extension tags
|
Used for handling xsd:extension tags
|
||||||
|
@ -277,6 +339,9 @@ class ComplexType(AnyType):
|
||||||
TODO: Needs a rewrite where the child containers are responsible for
|
TODO: Needs a rewrite where the child containers are responsible for
|
||||||
the extend functionality.
|
the extend functionality.
|
||||||
|
|
||||||
|
:type base: zeep.xsd.types.base.Type
|
||||||
|
:rtype base: zeep.xsd.types.base.Type
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(base, ComplexType):
|
if isinstance(base, ComplexType):
|
||||||
base_attributes = base._attributes_unwrapped
|
base_attributes = base._attributes_unwrapped
|
||||||
|
@ -301,6 +366,9 @@ class ComplexType(AnyType):
|
||||||
# container a placeholder element).
|
# container a placeholder element).
|
||||||
element = []
|
element = []
|
||||||
if self._element and base_element:
|
if self._element and base_element:
|
||||||
|
self._element = self._element.resolve()
|
||||||
|
base_element = base_element.resolve()
|
||||||
|
|
||||||
element = self._element.clone(self._element.name)
|
element = self._element.clone(self._element.name)
|
||||||
if isinstance(base_element, OrderIndicator):
|
if isinstance(base_element, OrderIndicator):
|
||||||
if isinstance(self._element, Choice):
|
if isinstance(self._element, Choice):
|
||||||
|
@ -323,7 +391,10 @@ class ComplexType(AnyType):
|
||||||
new = self.__class__(
|
new = self.__class__(
|
||||||
element=element,
|
element=element,
|
||||||
attributes=attributes,
|
attributes=attributes,
|
||||||
qname=self.qname)
|
qname=self.qname,
|
||||||
|
is_global=self.is_global)
|
||||||
|
|
||||||
|
new._extension_types = base.accepted_types
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def restrict(self, base):
|
def restrict(self, base):
|
||||||
|
@ -332,6 +403,10 @@ class ComplexType(AnyType):
|
||||||
|
|
||||||
Used for handling xsd:restriction
|
Used for handling xsd:restriction
|
||||||
|
|
||||||
|
:type base: zeep.xsd.types.base.Type
|
||||||
|
:rtype base: zeep.xsd.types.base.Type
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
attributes = list(
|
attributes = list(
|
||||||
chain(base._attributes_unwrapped, self._attributes_unwrapped))
|
chain(base._attributes_unwrapped, self._attributes_unwrapped))
|
||||||
|
@ -344,33 +419,30 @@ class ComplexType(AnyType):
|
||||||
new_attributes['##any'] = attr
|
new_attributes['##any'] = attr
|
||||||
else:
|
else:
|
||||||
new_attributes[attr.qname.text] = attr
|
new_attributes[attr.qname.text] = attr
|
||||||
attributes = new_attributes.values()
|
attributes = list(new_attributes.values())
|
||||||
|
|
||||||
|
if base._element:
|
||||||
|
base._element.resolve()
|
||||||
|
|
||||||
new = self.__class__(
|
new = self.__class__(
|
||||||
element=self._element or base._element,
|
element=self._element or base._element,
|
||||||
attributes=attributes,
|
attributes=attributes,
|
||||||
qname=self.qname)
|
qname=self.qname,
|
||||||
|
is_global=self.is_global)
|
||||||
return new.resolve()
|
return new.resolve()
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=True):
|
||||||
if len(depth) > 0 and self.is_global:
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
parts = []
|
parts = []
|
||||||
depth += (self.name,)
|
|
||||||
for name, element in self.elements_nested:
|
for name, element in self.elements_nested:
|
||||||
# http://schemas.xmlsoap.org/soap/encoding/ contains cyclic type
|
part = element.signature(schema, standalone=False)
|
||||||
if isinstance(element, Element) and element.type == self:
|
|
||||||
continue
|
|
||||||
|
|
||||||
part = element.signature(depth)
|
|
||||||
parts.append(part)
|
parts.append(part)
|
||||||
|
|
||||||
for name, attribute in self.attributes:
|
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)
|
parts.append(part)
|
||||||
|
|
||||||
value = ', '.join(parts)
|
value = ', '.join(parts)
|
||||||
if len(depth) > 1:
|
if standalone:
|
||||||
value = '{%s}' % value
|
return '%s(%s)' % (self.get_prefixed_name(schema), value)
|
||||||
return value
|
else:
|
||||||
|
return value
|
||||||
|
|
|
@ -4,7 +4,7 @@ import six
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from zeep.exceptions import ValidationError
|
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
|
from zeep.xsd.types.any import AnyType
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -54,7 +54,7 @@ class AnySimpleType(AnyType):
|
||||||
return '%s(value)' % (self.__class__.__name__)
|
return '%s(value)' % (self.__class__.__name__)
|
||||||
|
|
||||||
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
|
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
|
||||||
context=None):
|
context=None, schema_type=None):
|
||||||
if xmlelement.text is None:
|
if xmlelement.text is None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
@ -70,10 +70,8 @@ class AnySimpleType(AnyType):
|
||||||
def render(self, parent, value, xsd_type=None, render_path=None):
|
def render(self, parent, value, xsd_type=None, render_path=None):
|
||||||
parent.text = self.xmlvalue(value)
|
parent.text = self.xmlvalue(value)
|
||||||
|
|
||||||
def signature(self, depth=()):
|
def signature(self, schema=None, standalone=True):
|
||||||
if self.qname.namespace == NS_XSD:
|
return self.get_prefixed_name(schema)
|
||||||
return 'xsd:%s' % self.name
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def validate(self, value, required=False):
|
def validate(self, value, required=False):
|
||||||
if required and value is None:
|
if required and value is None:
|
||||||
|
|
|
@ -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)
|
|
@ -1,10 +1,6 @@
|
||||||
from defusedxml.lxml import fromstring
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from six.moves import range
|
from six.moves import range
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
from zeep.exceptions import XMLSyntaxError
|
from zeep import ns
|
||||||
from zeep.parser import absolute_location
|
|
||||||
|
|
||||||
|
|
||||||
class NamePrefixGenerator(object):
|
class NamePrefixGenerator(object):
|
||||||
|
@ -31,34 +27,6 @@ class UniqueNameGenerator(object):
|
||||||
return name
|
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):
|
def max_occurs_iter(max_occurs, items=None):
|
||||||
assert max_occurs is not None
|
assert max_occurs is not None
|
||||||
generator = range(0, max_occurs if max_occurs != 'unbounded' else 2**31-1)
|
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:
|
else:
|
||||||
for i in generator:
|
for i in generator:
|
||||||
yield i
|
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
|
||||||
|
|
|
@ -33,11 +33,46 @@ class AnyObject(object):
|
||||||
return self.xsd_obj
|
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):
|
class CompoundValue(object):
|
||||||
|
"""Represents a data object for a specific xsd:complexType."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
values = OrderedDict()
|
values = OrderedDict()
|
||||||
|
|
||||||
|
# Can be done after unpickle
|
||||||
|
if self._xsd_type is None:
|
||||||
|
return
|
||||||
|
|
||||||
# Set default values
|
# Set default values
|
||||||
for container_name, container in self._xsd_type.elements_nested:
|
for container_name, container in self._xsd_type.elements_nested:
|
||||||
elm_values = container.default_value
|
elm_values = container.default_value
|
||||||
|
@ -56,6 +91,9 @@ class CompoundValue(object):
|
||||||
values[key] = value
|
values[key] = value
|
||||||
self.__values__ = values
|
self.__values__ = values
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
return (_unpickle_compound_value, (self.__class__.__name__, self.__values__,))
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
return self.__values__.__contains__(key)
|
return self.__values__.__contains__(key)
|
||||||
|
|
||||||
|
@ -107,6 +145,9 @@ class CompoundValue(object):
|
||||||
setattr(new, attr, value)
|
setattr(new, attr, value)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
def __json__(self):
|
||||||
|
return self.__values__
|
||||||
|
|
||||||
|
|
||||||
def _process_signature(xsd_type, args, kwargs):
|
def _process_signature(xsd_type, args, kwargs):
|
||||||
"""Return a dict with the args/kwargs mapped to the field name.
|
"""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)
|
available_kwargs.remove(attribute_name)
|
||||||
result[attribute_name] = kwargs[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:
|
if available_kwargs:
|
||||||
raise TypeError((
|
raise TypeError((
|
||||||
"%s() got an unexpected keyword argument %r. " +
|
"%s() got an unexpected keyword argument %r. " +
|
||||||
"Signature: (%s)"
|
"Signature: `%s`"
|
||||||
) % (
|
) % (
|
||||||
xsd_type.qname or 'ComplexType',
|
xsd_type.qname or 'ComplexType',
|
||||||
next(iter(available_kwargs)),
|
next(iter(available_kwargs)),
|
||||||
xsd_type.signature()))
|
xsd_type.signature(standalone=False)))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -4,14 +4,13 @@ import re
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from zeep import exceptions
|
|
||||||
from zeep.exceptions import XMLParseError
|
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.utils import as_qname, qname_attr
|
||||||
from zeep.xsd import elements as xsd_elements
|
from zeep.xsd import elements as xsd_elements
|
||||||
from zeep.xsd import types as xsd_types
|
from zeep.xsd import types as xsd_types
|
||||||
from zeep.xsd.const import xsd_ns
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -37,12 +36,36 @@ class SchemaVisitor(object):
|
||||||
"""Visitor which processes XSD files and registers global elements and
|
"""Visitor which processes XSD files and registers global elements and
|
||||||
types in the given schema.
|
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):
|
def __init__(self, schema, document):
|
||||||
self.document = document
|
self.document = document
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
self._includes = set()
|
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):
|
def process(self, node, parent):
|
||||||
visit_func = self.visitors.get(node.tag)
|
visit_func = self.visitors.get(node.tag)
|
||||||
if not visit_func:
|
if not visit_func:
|
||||||
|
@ -79,7 +102,10 @@ class SchemaVisitor(object):
|
||||||
return cls(node.tag, ref, self.schema, **kwargs)
|
return cls(node.tag, ref, self.schema, **kwargs)
|
||||||
|
|
||||||
def visit_schema(self, node):
|
def visit_schema(self, node):
|
||||||
"""
|
"""Visit the xsd:schema element and process all the child elements
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<schema
|
<schema
|
||||||
attributeFormDefault = (qualified | unqualified): unqualified
|
attributeFormDefault = (qualified | unqualified): unqualified
|
||||||
blockDefault = (#all | List of (extension | restriction | substitution) : ''
|
blockDefault = (#all | List of (extension | restriction | substitution) : ''
|
||||||
|
@ -97,6 +123,9 @@ class SchemaVisitor(object):
|
||||||
annotation*)*)
|
annotation*)*)
|
||||||
</schema>
|
</schema>
|
||||||
|
|
||||||
|
:param node: The XML node
|
||||||
|
:type node: lxml.etree._Element
|
||||||
|
|
||||||
"""
|
"""
|
||||||
assert node is not None
|
assert node is not None
|
||||||
|
|
||||||
|
@ -104,12 +133,14 @@ class SchemaVisitor(object):
|
||||||
self.document._element_form = node.get('elementFormDefault', 'unqualified')
|
self.document._element_form = node.get('elementFormDefault', 'unqualified')
|
||||||
self.document._attribute_form = node.get('attributeFormDefault', 'unqualified')
|
self.document._attribute_form = node.get('attributeFormDefault', 'unqualified')
|
||||||
|
|
||||||
parent = node
|
for child in node:
|
||||||
for node in node.iterchildren():
|
self.process(child, parent=node)
|
||||||
self.process(node, parent=parent)
|
|
||||||
|
|
||||||
def visit_import(self, node, parent):
|
def visit_import(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<import
|
<import
|
||||||
id = ID
|
id = ID
|
||||||
namespace = anyURI
|
namespace = anyURI
|
||||||
|
@ -117,6 +148,12 @@ class SchemaVisitor(object):
|
||||||
{any attributes with non-schema Namespace}...>
|
{any attributes with non-schema Namespace}...>
|
||||||
Content: (annotation?)
|
Content: (annotation?)
|
||||||
</import>
|
</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
|
schema_node = None
|
||||||
namespace = node.get('namespace')
|
namespace = node.get('namespace')
|
||||||
|
@ -136,7 +173,7 @@ class SchemaVisitor(object):
|
||||||
document = self.schema._get_schema_document(namespace, location)
|
document = self.schema._get_schema_document(namespace, location)
|
||||||
if document:
|
if document:
|
||||||
logger.debug("Returning existing schema: %r", location)
|
logger.debug("Returning existing schema: %r", location)
|
||||||
self.document.register_import(namespace, document)
|
self.register_import(namespace, document)
|
||||||
return document
|
return document
|
||||||
|
|
||||||
# Hardcode the mapping between the xml namespace and the xsd for now.
|
# Hardcode the mapping between the xml namespace and the xsd for now.
|
||||||
|
@ -153,7 +190,10 @@ class SchemaVisitor(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Load the XML
|
# 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
|
# Check if the xsd:import namespace matches the targetNamespace. If
|
||||||
# the xsd:import statement didn't specify a namespace then make sure
|
# the xsd:import statement didn't specify a namespace then make sure
|
||||||
|
@ -168,17 +208,26 @@ class SchemaVisitor(object):
|
||||||
sourceline=node.sourceline)
|
sourceline=node.sourceline)
|
||||||
|
|
||||||
schema = self.schema.create_new_document(schema_node, location)
|
schema = self.schema.create_new_document(schema_node, location)
|
||||||
self.document.register_import(namespace, schema)
|
self.register_import(namespace, schema)
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
def visit_include(self, node, parent):
|
def visit_include(self, node, parent):
|
||||||
"""
|
"""
|
||||||
<include
|
|
||||||
id = ID
|
Definition::
|
||||||
schemaLocation = anyURI
|
|
||||||
{any attributes with non-schema Namespace}...>
|
<include
|
||||||
Content: (annotation?)
|
id = ID
|
||||||
</include>
|
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'):
|
if not node.get('schemaLocation'):
|
||||||
raise NotImplementedError("schemaLocation is required")
|
raise NotImplementedError("schemaLocation is required")
|
||||||
|
@ -188,13 +237,49 @@ class SchemaVisitor(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
schema_node = load_external(
|
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)
|
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):
|
def visit_element(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<element
|
<element
|
||||||
abstract = Boolean : false
|
abstract = Boolean : false
|
||||||
block = (#all | List of (extension | restriction | substitution))
|
block = (#all | List of (extension | restriction | substitution))
|
||||||
|
@ -214,6 +299,12 @@ class SchemaVisitor(object):
|
||||||
Content: (annotation?, (
|
Content: (annotation?, (
|
||||||
(simpleType | complexType)?, (unique | key | keyref)*))
|
(simpleType | complexType)?, (unique | key | keyref)*))
|
||||||
</element>
|
</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
|
is_global = parent.tag == tags.schema
|
||||||
|
|
||||||
|
@ -272,16 +363,16 @@ class SchemaVisitor(object):
|
||||||
min_occurs=min_occurs, max_occurs=max_occurs, nillable=nillable,
|
min_occurs=min_occurs, max_occurs=max_occurs, nillable=nillable,
|
||||||
default=default, is_global=is_global)
|
default=default, is_global=is_global)
|
||||||
|
|
||||||
self.document._elm_instances.append(element)
|
|
||||||
|
|
||||||
# Only register global elements
|
# Only register global elements
|
||||||
if is_global:
|
if is_global:
|
||||||
self.document.register_element(qname, element)
|
self.register_element(qname, element)
|
||||||
return element
|
return element
|
||||||
|
|
||||||
def visit_attribute(self, node, parent):
|
def visit_attribute(self, node, parent):
|
||||||
"""Declares an attribute.
|
"""Declares an attribute.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<attribute
|
<attribute
|
||||||
default = string
|
default = string
|
||||||
fixed = string
|
fixed = string
|
||||||
|
@ -294,6 +385,12 @@ class SchemaVisitor(object):
|
||||||
{any attributes with non-schema Namespace...}>
|
{any attributes with non-schema Namespace...}>
|
||||||
Content: (annotation?, (simpleType?))
|
Content: (annotation?, (simpleType?))
|
||||||
</attribute>
|
</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
|
is_global = parent.tag == tags.schema
|
||||||
|
|
||||||
|
@ -303,9 +400,8 @@ class SchemaVisitor(object):
|
||||||
match = re.match('([^\[]+)', array_type)
|
match = re.match('([^\[]+)', array_type)
|
||||||
if match:
|
if match:
|
||||||
array_type = match.groups()[0]
|
array_type = match.groups()[0]
|
||||||
qname = as_qname(
|
qname = as_qname(array_type, node.nsmap)
|
||||||
array_type, node.nsmap, self.document._target_namespace)
|
array_type = UnresolvedType(qname, self.schema)
|
||||||
array_type = xsd_types.UnresolvedType(qname, self.schema)
|
|
||||||
|
|
||||||
# If the elment has a ref attribute then all other attributes cannot
|
# If the elment has a ref attribute then all other attributes cannot
|
||||||
# be present. Short circuit that here.
|
# be present. Short circuit that here.
|
||||||
|
@ -316,9 +412,8 @@ class SchemaVisitor(object):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
attribute_form = node.get('form', self.document._attribute_form)
|
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:
|
if attribute_form == 'qualified' or is_global:
|
||||||
name = qname
|
name = qname_attr(node, 'name', self.document._target_namespace)
|
||||||
else:
|
else:
|
||||||
name = etree.QName(node.get('name'))
|
name = etree.QName(node.get('name'))
|
||||||
|
|
||||||
|
@ -338,15 +433,16 @@ class SchemaVisitor(object):
|
||||||
|
|
||||||
attr = xsd_elements.Attribute(
|
attr = xsd_elements.Attribute(
|
||||||
name, type_=xsd_type, default=default, required=required)
|
name, type_=xsd_type, default=default, required=required)
|
||||||
self.document._elm_instances.append(attr)
|
|
||||||
|
|
||||||
# Only register global elements
|
# Only register global elements
|
||||||
if is_global:
|
if is_global:
|
||||||
self.document.register_attribute(qname, attr)
|
self.register_attribute(name, attr)
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
def visit_simple_type(self, node, parent):
|
def visit_simple_type(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
Definition::
|
||||||
|
|
||||||
<simpleType
|
<simpleType
|
||||||
final = (#all | (list | union | restriction))
|
final = (#all | (list | union | restriction))
|
||||||
id = ID
|
id = ID
|
||||||
|
@ -354,6 +450,12 @@ class SchemaVisitor(object):
|
||||||
{any attributes with non-schema Namespace}...>
|
{any attributes with non-schema Namespace}...>
|
||||||
Content: (annotation?, (restriction | list | union))
|
Content: (annotation?, (restriction | list | union))
|
||||||
</simpleType>
|
</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:
|
if parent.tag == tags.schema:
|
||||||
|
@ -369,8 +471,7 @@ class SchemaVisitor(object):
|
||||||
child = items[0]
|
child = items[0]
|
||||||
if child.tag == tags.restriction:
|
if child.tag == tags.restriction:
|
||||||
base_type = self.visit_restriction_simple_type(child, node)
|
base_type = self.visit_restriction_simple_type(child, node)
|
||||||
xsd_type = xsd_types.UnresolvedCustomType(
|
xsd_type = UnresolvedCustomType(qname, base_type, self.schema)
|
||||||
qname, base_type, self.schema)
|
|
||||||
|
|
||||||
elif child.tag == tags.list:
|
elif child.tag == tags.list:
|
||||||
xsd_type = self.visit_list(child, node)
|
xsd_type = self.visit_list(child, node)
|
||||||
|
@ -382,23 +483,30 @@ class SchemaVisitor(object):
|
||||||
|
|
||||||
assert xsd_type is not None
|
assert xsd_type is not None
|
||||||
if is_global:
|
if is_global:
|
||||||
self.document.register_type(qname, xsd_type)
|
self.register_type(qname, xsd_type)
|
||||||
return xsd_type
|
return xsd_type
|
||||||
|
|
||||||
def visit_complex_type(self, node, parent):
|
def visit_complex_type(self, node, parent):
|
||||||
"""
|
"""
|
||||||
<complexType
|
Definition::
|
||||||
abstract = Boolean : false
|
|
||||||
block = (#all | List of (extension | restriction))
|
<complexType
|
||||||
final = (#all | List of (extension | restriction))
|
abstract = Boolean : false
|
||||||
id = ID
|
block = (#all | List of (extension | restriction))
|
||||||
mixed = Boolean : false
|
final = (#all | List of (extension | restriction))
|
||||||
name = NCName
|
id = ID
|
||||||
{any attributes with non-schema Namespace...}>
|
mixed = Boolean : false
|
||||||
Content: (annotation?, (simpleContent | complexContent |
|
name = NCName
|
||||||
((group | all | choice | sequence)?,
|
{any attributes with non-schema Namespace...}>
|
||||||
((attribute | attributeGroup)*, anyAttribute?))))
|
Content: (annotation?, (simpleContent | complexContent |
|
||||||
</complexType>
|
((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 = []
|
children = []
|
||||||
|
@ -448,22 +556,30 @@ class SchemaVisitor(object):
|
||||||
element=element, attributes=attributes, qname=qname,
|
element=element, attributes=attributes, qname=qname,
|
||||||
is_global=is_global)
|
is_global=is_global)
|
||||||
else:
|
else:
|
||||||
xsd_type = xsd_cls(qname=qname)
|
xsd_type = xsd_cls(qname=qname, is_global=is_global)
|
||||||
|
|
||||||
if is_global:
|
if is_global:
|
||||||
self.document.register_type(qname, xsd_type)
|
self.register_type(qname, xsd_type)
|
||||||
return 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
|
"""The complexContent element defines extensions or restrictions on a
|
||||||
complex type that contains mixed content or elements only.
|
complex type that contains mixed content or elements only.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<complexContent
|
<complexContent
|
||||||
id = ID
|
id = ID
|
||||||
mixed = Boolean
|
mixed = Boolean
|
||||||
{any attributes with non-schema Namespace}...>
|
{any attributes with non-schema Namespace}...>
|
||||||
Content: (annotation?, (restriction | extension))
|
Content: (annotation?, (restriction | extension))
|
||||||
</complexContent>
|
</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]
|
child = node.getchildren()[-1]
|
||||||
|
@ -485,16 +601,24 @@ class SchemaVisitor(object):
|
||||||
'extension': base,
|
'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
|
"""Contains extensions or restrictions on a complexType element with
|
||||||
character data or a simpleType element as content and contains no
|
character data or a simpleType element as content and contains no
|
||||||
elements.
|
elements.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<simpleContent
|
<simpleContent
|
||||||
id = ID
|
id = ID
|
||||||
{any attributes with non-schema Namespace}...>
|
{any attributes with non-schema Namespace}...>
|
||||||
Content: (annotation?, (restriction | extension))
|
Content: (annotation?, (restriction | extension))
|
||||||
</simpleContent>
|
</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]
|
child = node.getchildren()[-1]
|
||||||
|
@ -505,8 +629,10 @@ class SchemaVisitor(object):
|
||||||
return self.visit_extension_simple_content(child, node)
|
return self.visit_extension_simple_content(child, node)
|
||||||
raise AssertionError("Expected restriction or extension")
|
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
|
<restriction
|
||||||
base = QName
|
base = QName
|
||||||
id = ID
|
id = ID
|
||||||
|
@ -517,6 +643,12 @@ class SchemaVisitor(object):
|
||||||
totalDigits |fractionDigits | length | minLength |
|
totalDigits |fractionDigits | length | minLength |
|
||||||
maxLength | enumeration | whiteSpace | pattern)*))
|
maxLength | enumeration | whiteSpace | pattern)*))
|
||||||
</restriction>
|
</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_name = qname_attr(node, 'base')
|
||||||
if base_name:
|
if base_name:
|
||||||
|
@ -526,8 +658,10 @@ class SchemaVisitor(object):
|
||||||
if children[0].tag == tags.simpleType:
|
if children[0].tag == tags.simpleType:
|
||||||
return self.visit_simple_type(children[0], node)
|
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
|
<restriction
|
||||||
base = QName
|
base = QName
|
||||||
id = ID
|
id = ID
|
||||||
|
@ -539,14 +673,22 @@ class SchemaVisitor(object):
|
||||||
maxLength | enumeration | whiteSpace | pattern)*
|
maxLength | enumeration | whiteSpace | pattern)*
|
||||||
)?, ((attribute | attributeGroup)*, anyAttribute?))
|
)?, ((attribute | attributeGroup)*, anyAttribute?))
|
||||||
</restriction>
|
</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_name = qname_attr(node, 'base')
|
||||||
base_type = self._get_type(base_name)
|
base_type = self._get_type(base_name)
|
||||||
return base_type, []
|
return base_type, []
|
||||||
|
|
||||||
def visit_restriction_complex_content(self, node, parent, namespace=None):
|
def visit_restriction_complex_content(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<restriction
|
<restriction
|
||||||
base = QName
|
base = QName
|
||||||
id = ID
|
id = ID
|
||||||
|
@ -554,6 +696,12 @@ class SchemaVisitor(object):
|
||||||
Content: (annotation?, (group | all | choice | sequence)?,
|
Content: (annotation?, (group | all | choice | sequence)?,
|
||||||
((attribute | attributeGroup)*, anyAttribute?))
|
((attribute | attributeGroup)*, anyAttribute?))
|
||||||
</restriction>
|
</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_name = qname_attr(node, 'base')
|
||||||
base_type = self._get_type(base_name)
|
base_type = self._get_type(base_name)
|
||||||
|
@ -572,6 +720,9 @@ class SchemaVisitor(object):
|
||||||
|
|
||||||
def visit_extension_complex_content(self, node, parent):
|
def visit_extension_complex_content(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<extension
|
<extension
|
||||||
base = QName
|
base = QName
|
||||||
id = ID
|
id = ID
|
||||||
|
@ -580,6 +731,12 @@ class SchemaVisitor(object):
|
||||||
(group | all | choice | sequence)?,
|
(group | all | choice | sequence)?,
|
||||||
((attribute | attributeGroup)*, anyAttribute?)))
|
((attribute | attributeGroup)*, anyAttribute?)))
|
||||||
</extension>
|
</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_name = qname_attr(node, 'base')
|
||||||
base_type = self._get_type(base_name)
|
base_type = self._get_type(base_name)
|
||||||
|
@ -599,6 +756,9 @@ class SchemaVisitor(object):
|
||||||
|
|
||||||
def visit_extension_simple_content(self, node, parent):
|
def visit_extension_simple_content(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<extension
|
<extension
|
||||||
base = QName
|
base = QName
|
||||||
id = ID
|
id = ID
|
||||||
|
@ -616,16 +776,27 @@ class SchemaVisitor(object):
|
||||||
def visit_annotation(self, node, parent):
|
def visit_annotation(self, node, parent):
|
||||||
"""Defines an annotation.
|
"""Defines an annotation.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<annotation
|
<annotation
|
||||||
id = ID
|
id = ID
|
||||||
{any attributes with non-schema Namespace}...>
|
{any attributes with non-schema Namespace}...>
|
||||||
Content: (appinfo | documentation)*
|
Content: (appinfo | documentation)*
|
||||||
</annotation>
|
</annotation>
|
||||||
|
|
||||||
|
:param node: The XML node
|
||||||
|
:type node: lxml.etree._Element
|
||||||
|
:param parent: The parent XML node
|
||||||
|
:type parent: lxml.etree._Element
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return
|
return
|
||||||
|
|
||||||
def visit_any(self, node, parent):
|
def visit_any(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<any
|
<any
|
||||||
id = ID
|
id = ID
|
||||||
maxOccurs = (nonNegativeInteger | unbounded) : 1
|
maxOccurs = (nonNegativeInteger | unbounded) : 1
|
||||||
|
@ -636,6 +807,12 @@ class SchemaVisitor(object):
|
||||||
{any attributes with non-schema Namespace...}>
|
{any attributes with non-schema Namespace...}>
|
||||||
Content: (annotation?)
|
Content: (annotation?)
|
||||||
</any>
|
</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)
|
min_occurs, max_occurs = _process_occurs_attrs(node)
|
||||||
process_contents = node.get('processContents', 'strict')
|
process_contents = node.get('processContents', 'strict')
|
||||||
|
@ -645,6 +822,8 @@ class SchemaVisitor(object):
|
||||||
|
|
||||||
def visit_sequence(self, node, parent):
|
def visit_sequence(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
Definition::
|
||||||
|
|
||||||
<sequence
|
<sequence
|
||||||
id = ID
|
id = ID
|
||||||
maxOccurs = (nonNegativeInteger | unbounded) : 1
|
maxOccurs = (nonNegativeInteger | unbounded) : 1
|
||||||
|
@ -653,6 +832,12 @@ class SchemaVisitor(object):
|
||||||
Content: (annotation?,
|
Content: (annotation?,
|
||||||
(element | group | choice | sequence | any)*)
|
(element | group | choice | sequence | any)*)
|
||||||
</sequence>
|
</sequence>
|
||||||
|
|
||||||
|
:param node: The XML node
|
||||||
|
:type node: lxml.etree._Element
|
||||||
|
:param parent: The parent XML node
|
||||||
|
:type parent: lxml.etree._Element
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sub_types = [
|
sub_types = [
|
||||||
|
@ -677,6 +862,8 @@ class SchemaVisitor(object):
|
||||||
"""Allows the elements in the group to appear (or not appear) in any
|
"""Allows the elements in the group to appear (or not appear) in any
|
||||||
order in the containing element.
|
order in the containing element.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<all
|
<all
|
||||||
id = ID
|
id = ID
|
||||||
maxOccurs= 1: 1
|
maxOccurs= 1: 1
|
||||||
|
@ -684,6 +871,12 @@ class SchemaVisitor(object):
|
||||||
{any attributes with non-schema Namespace...}>
|
{any attributes with non-schema Namespace...}>
|
||||||
Content: (annotation?, element*)
|
Content: (annotation?, element*)
|
||||||
</all>
|
</all>
|
||||||
|
|
||||||
|
:param node: The XML node
|
||||||
|
:type node: lxml.etree._Element
|
||||||
|
:param parent: The parent XML node
|
||||||
|
:type parent: lxml.etree._Element
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sub_types = [
|
sub_types = [
|
||||||
|
@ -703,6 +896,8 @@ class SchemaVisitor(object):
|
||||||
"""Groups a set of element declarations so that they can be
|
"""Groups a set of element declarations so that they can be
|
||||||
incorporated as a group into complex type definitions.
|
incorporated as a group into complex type definitions.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<group
|
<group
|
||||||
name= NCName
|
name= NCName
|
||||||
id = ID
|
id = ID
|
||||||
|
@ -714,9 +909,16 @@ class SchemaVisitor(object):
|
||||||
Content: (annotation?, (all | choice | sequence))
|
Content: (annotation?, (all | choice | sequence))
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
"""
|
:param node: The XML node
|
||||||
|
:type node: lxml.etree._Element
|
||||||
|
:param parent: The parent XML node
|
||||||
|
:type parent: lxml.etree._Element
|
||||||
|
|
||||||
result = self.process_reference(node)
|
"""
|
||||||
|
min_occurs, max_occurs = _process_occurs_attrs(node)
|
||||||
|
|
||||||
|
result = self.process_reference(
|
||||||
|
node, min_occurs=min_occurs, max_occurs=max_occurs)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -730,11 +932,13 @@ class SchemaVisitor(object):
|
||||||
elm = xsd_elements.Group(name=qname, child=item)
|
elm = xsd_elements.Group(name=qname, child=item)
|
||||||
|
|
||||||
if parent.tag == tags.schema:
|
if parent.tag == tags.schema:
|
||||||
self.document.register_group(qname, elm)
|
self.register_group(qname, elm)
|
||||||
return elm
|
return elm
|
||||||
|
|
||||||
def visit_list(self, node, parent):
|
def visit_list(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
Definition::
|
||||||
|
|
||||||
<list
|
<list
|
||||||
id = ID
|
id = ID
|
||||||
itemType = QName
|
itemType = QName
|
||||||
|
@ -745,6 +949,12 @@ class SchemaVisitor(object):
|
||||||
The use of the simpleType element child and the itemType attribute is
|
The use of the simpleType element child and the itemType attribute is
|
||||||
mutually exclusive.
|
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')
|
item_type = qname_attr(node, 'itemType')
|
||||||
if item_type:
|
if item_type:
|
||||||
|
@ -757,6 +967,8 @@ class SchemaVisitor(object):
|
||||||
|
|
||||||
def visit_choice(self, node, parent):
|
def visit_choice(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
Definition::
|
||||||
|
|
||||||
<choice
|
<choice
|
||||||
id = ID
|
id = ID
|
||||||
maxOccurs= (nonNegativeInteger | unbounded) : 1
|
maxOccurs= (nonNegativeInteger | unbounded) : 1
|
||||||
|
@ -780,19 +992,27 @@ class SchemaVisitor(object):
|
||||||
def visit_union(self, node, parent):
|
def visit_union(self, node, parent):
|
||||||
"""Defines a collection of multiple simpleType definitions.
|
"""Defines a collection of multiple simpleType definitions.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<union
|
<union
|
||||||
id = ID
|
id = ID
|
||||||
memberTypes = List of QNames
|
memberTypes = List of QNames
|
||||||
{any attributes with non-schema Namespace}...>
|
{any attributes with non-schema Namespace}...>
|
||||||
Content: (annotation?, (simpleType*))
|
Content: (annotation?, (simpleType*))
|
||||||
</union>
|
</union>
|
||||||
|
|
||||||
|
:param node: The XML node
|
||||||
|
:type node: lxml.etree._Element
|
||||||
|
:param parent: The parent XML node
|
||||||
|
:type parent: lxml.etree._Element
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# TODO
|
# TODO
|
||||||
members = node.get('memberTypes')
|
members = node.get('memberTypes')
|
||||||
types = []
|
types = []
|
||||||
if members:
|
if members:
|
||||||
for member in members.split():
|
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)
|
xsd_type = self._get_type(qname)
|
||||||
types.append(xsd_type)
|
types.append(xsd_type)
|
||||||
else:
|
else:
|
||||||
|
@ -805,18 +1025,28 @@ class SchemaVisitor(object):
|
||||||
attribute or element values) must be unique within the specified scope.
|
attribute or element values) must be unique within the specified scope.
|
||||||
The value must be unique or nil.
|
The value must be unique or nil.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<unique
|
<unique
|
||||||
id = ID
|
id = ID
|
||||||
name = NCName
|
name = NCName
|
||||||
{any attributes with non-schema Namespace}...>
|
{any attributes with non-schema Namespace}...>
|
||||||
Content: (annotation?, (selector, field+))
|
Content: (annotation?, (selector, field+))
|
||||||
</unique>
|
</unique>
|
||||||
|
|
||||||
|
:param node: The XML node
|
||||||
|
:type node: lxml.etree._Element
|
||||||
|
:param parent: The parent XML node
|
||||||
|
:type parent: lxml.etree._Element
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# TODO
|
# TODO
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def visit_attribute_group(self, node, parent):
|
def visit_attribute_group(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
Definition::
|
||||||
|
|
||||||
<attributeGroup
|
<attributeGroup
|
||||||
id = ID
|
id = ID
|
||||||
name = NCName
|
name = NCName
|
||||||
|
@ -825,6 +1055,12 @@ class SchemaVisitor(object):
|
||||||
Content: (annotation?),
|
Content: (annotation?),
|
||||||
((attribute | attributeGroup)*, anyAttribute?))
|
((attribute | attributeGroup)*, anyAttribute?))
|
||||||
</attributeGroup>
|
</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)
|
ref = self.process_reference(node)
|
||||||
if ref:
|
if ref:
|
||||||
|
@ -835,10 +1071,12 @@ class SchemaVisitor(object):
|
||||||
|
|
||||||
attributes = self._process_attributes(node, children)
|
attributes = self._process_attributes(node, children)
|
||||||
attribute_group = xsd_elements.AttributeGroup(qname, attributes)
|
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):
|
def visit_any_attribute(self, node, parent):
|
||||||
"""
|
"""
|
||||||
|
Definition::
|
||||||
|
|
||||||
<anyAttribute
|
<anyAttribute
|
||||||
id = ID
|
id = ID
|
||||||
namespace = ((##any | ##other) |
|
namespace = ((##any | ##other) |
|
||||||
|
@ -847,6 +1085,12 @@ class SchemaVisitor(object):
|
||||||
{any attributes with non-schema Namespace...}>
|
{any attributes with non-schema Namespace...}>
|
||||||
Content: (annotation?)
|
Content: (annotation?)
|
||||||
</anyAttribute>
|
</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')
|
process_contents = node.get('processContents', 'strict')
|
||||||
return xsd_elements.AnyAttribute(process_contents=process_contents)
|
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
|
non-XML data within an XML document. An XML Schema notation declaration
|
||||||
is a reconstruction of XML 1.0 NOTATION declarations.
|
is a reconstruction of XML 1.0 NOTATION declarations.
|
||||||
|
|
||||||
|
Definition::
|
||||||
|
|
||||||
<notation
|
<notation
|
||||||
id = ID
|
id = ID
|
||||||
name = NCName
|
name = NCName
|
||||||
|
@ -865,17 +1111,18 @@ class SchemaVisitor(object):
|
||||||
Content: (annotation?)
|
Content: (annotation?)
|
||||||
</notation>
|
</notation>
|
||||||
|
|
||||||
|
:param node: The XML node
|
||||||
|
:type node: lxml.etree._Element
|
||||||
|
:param parent: The parent XML node
|
||||||
|
:type parent: lxml.etree._Element
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _get_type(self, name):
|
def _get_type(self, name):
|
||||||
assert name is not None
|
assert name is not None
|
||||||
name = self._create_qname(name)
|
name = self._create_qname(name)
|
||||||
try:
|
return UnresolvedType(name, self.schema)
|
||||||
retval = self.schema.get_type(name)
|
|
||||||
except (exceptions.NamespaceError, exceptions.LookupError):
|
|
||||||
retval = xsd_types.UnresolvedType(name, self.schema)
|
|
||||||
return retval
|
|
||||||
|
|
||||||
def _create_qname(self, name):
|
def _create_qname(self, name):
|
||||||
if not isinstance(name, etree.QName):
|
if not isinstance(name, etree.QName):
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
from pretend import stub
|
from pretend import stub
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
import aiohttp
|
||||||
from aioresponses import aioresponses
|
from aioresponses import aioresponses
|
||||||
|
|
||||||
from zeep import cache, asyncio
|
from zeep import cache, asyncio
|
||||||
|
@ -39,3 +40,21 @@ async def test_post(event_loop):
|
||||||
headers={})
|
headers={})
|
||||||
|
|
||||||
assert result.content == b'x'
|
assert result.content == b'x'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requests
|
||||||
|
@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
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requests
|
||||||
|
@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
|
||||||
|
|
|
@ -11,6 +11,18 @@ def test_factory_namespace():
|
||||||
assert obj.NameLast == 'van Tellingen'
|
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():
|
def test_factory_ns_auto_prefix():
|
||||||
client = Client('tests/wsdl_files/soap.wsdl')
|
client = Client('tests/wsdl_files/soap.wsdl')
|
||||||
factory = client.type_factory('ns0')
|
factory = client.type_factory('ns0')
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@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'
|
|
@ -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]
|
request = m.request_history[0]
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
import pytest
|
||||||
from lxml import etree
|
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
|
from zeep import xsd
|
||||||
|
|
||||||
|
|
||||||
def get_transport():
|
@pytest.fixture(scope='function')
|
||||||
|
def transport():
|
||||||
transport = DummyTransport()
|
transport = DummyTransport()
|
||||||
transport.bind(
|
transport.bind(
|
||||||
'http://schemas.xmlsoap.org/soap/encoding/',
|
'http://schemas.xmlsoap.org/soap/encoding/',
|
||||||
|
@ -14,7 +16,7 @@ def get_transport():
|
||||||
return transport
|
return transport
|
||||||
|
|
||||||
|
|
||||||
def test_simple_type():
|
def test_simple_type(transport):
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<xsd:schema
|
<xsd:schema
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -30,7 +32,7 @@ def test_simple_type():
|
||||||
</xsd:complexContent>
|
</xsd:complexContent>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
"""), transport=get_transport())
|
"""), transport=transport)
|
||||||
|
|
||||||
ArrayOfString = schema.get_type('ns0:ArrayOfString')
|
ArrayOfString = schema.get_type('ns0:ArrayOfString')
|
||||||
print(ArrayOfString.__dict__)
|
print(ArrayOfString.__dict__)
|
||||||
|
@ -52,8 +54,105 @@ def test_simple_type():
|
||||||
|
|
||||||
assert_nodes_equal(expected, node)
|
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("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<xsd:schema
|
<xsd:schema
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -80,7 +179,7 @@ def test_complex_type():
|
||||||
</xsd:complexContent>
|
</xsd:complexContent>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
"""), transport=get_transport())
|
"""), transport=transport)
|
||||||
|
|
||||||
ArrayOfObject = schema.get_type('ns0:ArrayOfObject')
|
ArrayOfObject = schema.get_type('ns0:ArrayOfObject')
|
||||||
ArrayObject = schema.get_type('ns0:ArrayObject')
|
ArrayObject = schema.get_type('ns0:ArrayObject')
|
||||||
|
@ -111,9 +210,17 @@ def test_complex_type():
|
||||||
</document>
|
</document>
|
||||||
"""
|
"""
|
||||||
assert_nodes_equal(expected, node)
|
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("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<xsd:schema
|
<xsd:schema
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -137,7 +244,7 @@ def test_complex_type_without_name():
|
||||||
</xsd:complexContent>
|
</xsd:complexContent>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
"""), transport=get_transport())
|
"""), transport=transport)
|
||||||
|
|
||||||
ArrayOfObject = schema.get_type('ns0:ArrayOfObject')
|
ArrayOfObject = schema.get_type('ns0:ArrayOfObject')
|
||||||
ArrayObject = schema.get_type('ns0:ArrayObject')
|
ArrayObject = schema.get_type('ns0:ArrayObject')
|
||||||
|
@ -170,13 +277,13 @@ def test_complex_type_without_name():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
data = ArrayOfObject.parse_xmlelement(node, schema)
|
data = ArrayOfObject.parse_xmlelement(node, schema)
|
||||||
|
|
||||||
assert len(data._value_1) == 3
|
assert len(data) == 3
|
||||||
assert data._value_1[0]['attr_1'] == 'attr-1'
|
assert data[0]['attr_1'] == 'attr-1'
|
||||||
assert data._value_1[0]['attr_2'] == 'attr-2'
|
assert data[0]['attr_2'] == 'attr-2'
|
||||||
assert data._value_1[1]['attr_1'] == 'attr-3'
|
assert data[1]['attr_1'] == 'attr-3'
|
||||||
assert data._value_1[1]['attr_2'] == 'attr-4'
|
assert data[1]['attr_2'] == 'attr-4'
|
||||||
assert data._value_1[2]['attr_1'] == 'attr-5'
|
assert data[2]['attr_1'] == 'attr-5'
|
||||||
assert data._value_1[2]['attr_2'] == 'attr-6'
|
assert data[2]['attr_2'] == 'attr-6'
|
||||||
|
|
||||||
|
|
||||||
def test_soap_array_parse_remote_ns():
|
def test_soap_array_parse_remote_ns():
|
||||||
|
@ -237,8 +344,8 @@ def test_soap_array_parse_remote_ns():
|
||||||
elm = schema.get_element('ns0:countries')
|
elm = schema.get_element('ns0:countries')
|
||||||
data = elm.parse(doc, schema)
|
data = elm.parse(doc, schema)
|
||||||
|
|
||||||
assert data._value_1[0].code == 'NL'
|
assert data[0].code == 'NL'
|
||||||
assert data._value_1[0].name == 'The Netherlands'
|
assert data[0].name == 'The Netherlands'
|
||||||
|
|
||||||
|
|
||||||
def test_wsdl_array_type():
|
def test_wsdl_array_type():
|
||||||
|
@ -284,9 +391,12 @@ def test_wsdl_array_type():
|
||||||
|
|
||||||
array = array_elm([item_1, item_2])
|
array = array_elm([item_1, item_2])
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
assert array_elm.signature() == (
|
assert array_elm.signature(schema=schema) == 'ns0:array(ns0:array)'
|
||||||
'_value_1: base[], arrayType: xsd:string, offset: arrayCoordinate, ' +
|
|
||||||
'id: xsd:ID, href: xsd:anyURI, _attr_1: {}')
|
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)
|
array_elm.render(node, array)
|
||||||
expected = """
|
expected = """
|
||||||
<document>
|
<document>
|
||||||
|
@ -363,7 +473,80 @@ def test_soap_array_parse():
|
||||||
|
|
||||||
elm = schema.get_element('ns0:FlagDetailsList')
|
elm = schema.get_element('ns0:FlagDetailsList')
|
||||||
data = elm.parse(doc, schema)
|
data = elm.parse(doc, schema)
|
||||||
assert data.FlagDetailsStruct[0].Name == 'flag1'
|
assert data[0].Name == 'flag1'
|
||||||
assert data.FlagDetailsStruct[0].Value == 'value1'
|
assert data[0].Value == 'value1'
|
||||||
assert data.FlagDetailsStruct[1].Name == 'flag2'
|
assert data[1].Name == 'flag2'
|
||||||
assert data.FlagDetailsStruct[1].Value == 'value2'
|
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"
|
||||||
|
|
|
@ -54,14 +54,14 @@ def test_parse():
|
||||||
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
||||||
operation = binding.get('TestOperation')
|
operation = binding.get('TestOperation')
|
||||||
|
|
||||||
assert operation.input.body.signature() == 'xsd:string'
|
assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
|
||||||
assert operation.input.header.signature() == ''
|
assert operation.input.header.signature(schema=root.types) == 'soap-env:Header()'
|
||||||
assert operation.input.envelope.signature() == 'body: xsd:string'
|
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.input.signature(as_output=False) == 'xsd:string'
|
||||||
|
|
||||||
assert operation.output.body.signature() == 'xsd:string'
|
assert operation.output.body.signature(schema=root.types) == 'ns0:Response(xsd:string)'
|
||||||
assert operation.output.header.signature() == ''
|
assert operation.output.header.signature(schema=root.types) == 'soap-env:Header()'
|
||||||
assert operation.output.envelope.signature() == 'body: xsd:string'
|
assert operation.output.envelope.signature(schema=root.types) == 'soap-env:envelope(body: xsd:string)'
|
||||||
assert operation.output.signature(as_output=True) == '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']
|
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
||||||
operation = binding.get('TestOperation')
|
operation = binding.get('TestOperation')
|
||||||
|
|
||||||
assert operation.input.body.signature() == ''
|
assert operation.input.body.signature(schema=root.types) == 'soap-env:Body()'
|
||||||
assert operation.input.header.signature() == ''
|
assert operation.input.header.signature(schema=root.types) == 'soap-env:Header()'
|
||||||
assert operation.input.envelope.signature() == 'body: {}'
|
assert operation.input.envelope.signature(schema=root.types) == 'soap-env:envelope(body: {})'
|
||||||
assert operation.input.signature(as_output=False) == ''
|
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']
|
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
||||||
operation = binding.get('TestOperation')
|
operation = binding.get('TestOperation')
|
||||||
|
|
||||||
assert operation.input.body.signature() == 'xsd:string'
|
assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
|
||||||
assert operation.input.header.signature() == 'auth: RequestHeader()'
|
assert operation.input.header.signature(schema=root.types) == 'soap-env:Header(auth: xsd:string)'
|
||||||
assert operation.input.envelope.signature() == 'body: xsd:string, header: {auth: RequestHeader()}' # noqa
|
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: RequestHeader()}' # 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.body.signature(schema=root.types) == 'ns0:Response(xsd:string)'
|
||||||
assert operation.output.header.signature() == 'auth: ResponseHeader()'
|
assert operation.output.header.signature(schema=root.types) == 'soap-env:Header(auth: xsd:string)'
|
||||||
assert operation.output.envelope.signature() == 'body: xsd:string, header: {auth: ResponseHeader()}' # noqa
|
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) == 'body: xsd:string, header: {auth: ResponseHeader()}' # noqa
|
assert operation.output.signature(as_output=True) == 'header: {auth: xsd:string}, body: xsd:string' # noqa
|
||||||
|
|
||||||
|
|
||||||
def test_parse_with_header_type():
|
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']
|
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
||||||
operation = binding.get('TestOperation')
|
operation = binding.get('TestOperation')
|
||||||
|
|
||||||
assert operation.input.body.signature() == 'xsd:string'
|
assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
|
||||||
assert operation.input.header.signature() == 'auth: RequestHeaderType'
|
assert operation.input.header.signature(schema=root.types) == 'soap-env:Header(auth: ns0:RequestHeaderType)'
|
||||||
assert operation.input.envelope.signature() == 'body: xsd:string, header: {auth: RequestHeaderType}' # noqa
|
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: RequestHeaderType}' # 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.body.signature(schema=root.types) == 'ns0:Response(xsd:string)'
|
||||||
assert operation.output.header.signature() == 'auth: ResponseHeaderType'
|
assert operation.output.header.signature(schema=root.types) == 'soap-env:Header(auth: ns0:ResponseHeaderType)'
|
||||||
assert operation.output.envelope.signature() == 'body: xsd:string, header: {auth: ResponseHeaderType}' # noqa
|
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) == 'body: xsd:string, header: {auth: ResponseHeaderType}' # noqa
|
assert operation.output.signature(as_output=True) == 'header: {auth: ns0:ResponseHeaderType}, body: xsd:string' # noqa
|
||||||
|
|
||||||
|
|
||||||
def test_parse_with_header_other_message():
|
def test_parse_with_header_other_message():
|
||||||
|
@ -292,12 +292,13 @@ def test_parse_with_header_other_message():
|
||||||
""".strip())
|
""".strip())
|
||||||
|
|
||||||
root = wsdl.Document(wsdl_content, None)
|
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']
|
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
||||||
operation = binding.get('TestOperation')
|
operation = binding.get('TestOperation')
|
||||||
|
|
||||||
assert operation.input.header.signature() == 'header: RequestHeader()'
|
assert operation.input.header.signature(schema=root.types) == 'soap-env:Header(header: xsd:string)'
|
||||||
assert operation.input.body.signature() == 'xsd:string'
|
assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
|
||||||
|
|
||||||
header = root.types.get_element(
|
header = root.types.get_element(
|
||||||
'{http://tests.python-zeep.org/tns}RequestHeader'
|
'{http://tests.python-zeep.org/tns}RequestHeader'
|
||||||
|
@ -1193,7 +1194,7 @@ def test_deserialize_with_headers():
|
||||||
serialized = operation.process_reply(response_body)
|
serialized = operation.process_reply(response_body)
|
||||||
|
|
||||||
assert operation.output.signature(as_output=True) == (
|
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_1.arg1 == 'ah1'
|
||||||
assert serialized.body.request_2.arg2 == 'ah2'
|
assert serialized.body.request_2.arg2 == 'ah2'
|
||||||
assert serialized.header.header_1.username == 'mvantellingen'
|
assert serialized.header.header_1.username == 'mvantellingen'
|
||||||
|
|
|
@ -52,10 +52,10 @@ def test_urlencoded_serialize():
|
||||||
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
||||||
operation = binding.get('TestOperation')
|
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.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'
|
assert operation.output.signature(as_output=True) == 'xsd:string'
|
||||||
|
|
||||||
serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
|
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']
|
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
||||||
operation = binding.get('TestOperation')
|
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.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'
|
assert operation.output.signature(as_output=True) == 'xsd:string'
|
||||||
|
|
||||||
serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
|
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']
|
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
||||||
operation = binding.get('TestOperation')
|
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.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'
|
assert operation.output.signature(as_output=True) == 'xsd:string'
|
||||||
|
|
||||||
serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
|
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']
|
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
|
||||||
operation = binding.get('TestOperation')
|
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.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'
|
assert operation.output.signature(as_output=True) == 'xsd:string'
|
||||||
|
|
||||||
serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
|
serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
|
||||||
|
|
|
@ -1,12 +1,36 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from pretend import stub
|
from pretend import stub
|
||||||
|
|
||||||
from tests.utils import load_xml
|
from tests.utils import load_xml
|
||||||
from zeep import Client
|
from zeep import Client
|
||||||
from zeep.exceptions import Fault
|
from zeep.exceptions import Fault
|
||||||
|
from zeep.exceptions import TransportError
|
||||||
from zeep.wsdl import bindings
|
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():
|
def test_soap11_process_error():
|
||||||
response = load_xml("""
|
response = load_xml("""
|
||||||
<soapenv:Envelope
|
<soapenv:Envelope
|
||||||
|
@ -26,10 +50,10 @@ def test_soap11_process_error():
|
||||||
</soapenv:Body>
|
</soapenv:Body>
|
||||||
</soapenv:Envelope>
|
</soapenv:Envelope>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
binding = bindings.Soap11Binding(
|
binding = bindings.Soap11Binding(
|
||||||
wsdl=None, name=None, port_name=None, transport=None,
|
wsdl=None, name=None, port_name=None, transport=None,
|
||||||
default_style=None)
|
default_style=None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
binding.process_error(response, None)
|
binding.process_error(response, None)
|
||||||
assert False
|
assert False
|
||||||
|
@ -112,6 +136,79 @@ def test_soap12_process_error():
|
||||||
assert exc.subcodes[1].localname == 'fault-subcode2'
|
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():
|
def test_mime_multipart():
|
||||||
data = '\r\n'.join(line.strip() for line in """
|
data = '\r\n'.join(line.strip() for line in """
|
||||||
--MIME_boundary
|
--MIME_boundary
|
||||||
|
@ -154,6 +251,7 @@ def test_mime_multipart():
|
||||||
response = stub(
|
response = stub(
|
||||||
status_code=200,
|
status_code=200,
|
||||||
content=data,
|
content=data,
|
||||||
|
encoding='utf-8',
|
||||||
headers={
|
headers={
|
||||||
'Content-Type': 'multipart/related; type="text/xml"; start="<claim061400a.xml@claiming-it.com>"; boundary="MIME_boundary"'
|
'Content-Type': 'multipart/related; type="text/xml"; start="<claim061400a.xml@claiming-it.com>"; boundary="MIME_boundary"'
|
||||||
}
|
}
|
||||||
|
@ -167,3 +265,95 @@ def test_mime_multipart():
|
||||||
|
|
||||||
assert result.attachments[0].content == b'...Base64 encoded TIFF image...'
|
assert result.attachments[0].content == b'...Base64 encoded TIFF image...'
|
||||||
assert result.attachments[1].content == b'...Raw JPEG 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@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@claiming-it.com"/>
|
||||||
|
<theCrashPhoto href="cid:claim061400a.jpeg@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@claiming-it.com>
|
||||||
|
|
||||||
|
Li4uQmFzZTY0IGVuY29kZWQgVElGRiBpbWFnZS4uLg==
|
||||||
|
|
||||||
|
--MIME_boundary
|
||||||
|
Content-Type: text/xml
|
||||||
|
Content-ID: <claim061400a.jpeg@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@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_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
|
||||||
|
|
|
@ -149,39 +149,6 @@ def test_invalid_kwarg_simple_type():
|
||||||
elm(something='is-wrong')
|
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():
|
def test_any():
|
||||||
|
@ -515,7 +482,7 @@ def test_duplicate_element_names():
|
||||||
))
|
))
|
||||||
|
|
||||||
# sequences
|
# 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
|
assert custom_type.signature() == expected
|
||||||
obj = custom_type(item='foo', item__1='bar', item__2='lala')
|
obj = custom_type(item='foo', item__1='bar', item__2='lala')
|
||||||
|
|
||||||
|
@ -548,7 +515,7 @@ def test_element_attribute_name_conflict():
|
||||||
))
|
))
|
||||||
|
|
||||||
# sequences
|
# 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
|
assert custom_type.signature() == expected
|
||||||
obj = custom_type(item='foo', foo='x', attr__item='bar')
|
obj = custom_type(item='foo', foo='x', attr__item='bar')
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import datetime
|
||||||
import pytest
|
import pytest
|
||||||
from lxml import etree
|
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
|
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():
|
def test_any_simple():
|
||||||
schema = get_any_schema()
|
schema = get_any_schema()
|
||||||
|
|
||||||
|
@ -109,6 +127,41 @@ def test_any_value_invalid():
|
||||||
container_elm.render(node, obj)
|
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():
|
def test_any_with_ref():
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
|
@ -219,6 +272,36 @@ def test_element_any_type():
|
||||||
item = container_elm.parse(node.getchildren()[0], schema)
|
item = container_elm.parse(node.getchildren()[0], schema)
|
||||||
assert item.something == 'bar'
|
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():
|
def test_element_any_type_elements():
|
||||||
node = etree.fromstring("""
|
node = etree.fromstring("""
|
||||||
|
@ -298,8 +381,8 @@ def test_any_in_nested_sequence():
|
||||||
""")) # noqa
|
""")) # noqa
|
||||||
|
|
||||||
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
|
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
|
||||||
assert container_elm.signature() == (
|
assert container_elm.signature(schema) == (
|
||||||
'items: {_value_1: ANY}, version: xsd:string, _value_1: ANY[]')
|
'ns0:container(items: {_value_1: ANY}, version: xsd:string, _value_1: ANY[])')
|
||||||
|
|
||||||
something = schema.get_element('{http://tests.python-zeep.org/}something')
|
something = schema.get_element('{http://tests.python-zeep.org/}something')
|
||||||
foobar = schema.get_element('{http://tests.python-zeep.org/}foobar')
|
foobar = schema.get_element('{http://tests.python-zeep.org/}foobar')
|
||||||
|
|
|
@ -26,8 +26,8 @@ def test_anyattribute():
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
|
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
|
||||||
assert container_elm.signature() == (
|
assert container_elm.signature(schema) == (
|
||||||
'foo: xsd:string, _attr_1: {}')
|
'ns0:container(foo: xsd:string, _attr_1: {})')
|
||||||
obj = container_elm(foo='bar', _attr_1=OrderedDict([
|
obj = container_elm(foo='bar', _attr_1=OrderedDict([
|
||||||
('hiep', 'hoi'), ('hoi', 'hiep')
|
('hiep', 'hoi'), ('hoi', 'hiep')
|
||||||
]))
|
]))
|
||||||
|
@ -73,7 +73,8 @@ def test_attribute_list_type():
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
|
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])
|
obj = container_elm(foo='bar', lijst=[1, 2, 3])
|
||||||
expected = """
|
expected = """
|
||||||
<document>
|
<document>
|
||||||
|
@ -341,7 +342,8 @@ def test_nested_attribute():
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
|
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'})
|
obj = container_elm(item={'x': 'foo', 'y': 'bar'})
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
|
@ -371,7 +373,7 @@ def test_attribute_union_type():
|
||||||
<restriction base="xsd:string"/>
|
<restriction base="xsd:string"/>
|
||||||
</simpleType>
|
</simpleType>
|
||||||
<simpleType name="parent">
|
<simpleType name="parent">
|
||||||
<union memberTypes="one two"/>
|
<union memberTypes="tns:one tns:two"/>
|
||||||
</simpleType>
|
</simpleType>
|
||||||
<simpleType name="two">
|
<simpleType name="two">
|
||||||
<restriction base="xsd:string"/>
|
<restriction base="xsd:string"/>
|
||||||
|
|
|
@ -30,6 +30,8 @@ class TestBoolean:
|
||||||
assert instance.xmlvalue(False) == 'false'
|
assert instance.xmlvalue(False) == 'false'
|
||||||
assert instance.xmlvalue(1) == 'true'
|
assert instance.xmlvalue(1) == 'true'
|
||||||
assert instance.xmlvalue(0) == 'false'
|
assert instance.xmlvalue(0) == 'false'
|
||||||
|
assert instance.xmlvalue('false') == 'false'
|
||||||
|
assert instance.xmlvalue('0') == 'false'
|
||||||
|
|
||||||
def test_pythonvalue(self):
|
def test_pythonvalue(self):
|
||||||
instance = builtins.Boolean()
|
instance = builtins.Boolean()
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import pytest
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
import pytest
|
||||||
|
|
||||||
from tests.utils import assert_nodes_equal, load_xml, render_node
|
from tests.utils import assert_nodes_equal, load_xml, render_node
|
||||||
from zeep import xsd
|
from zeep import xsd
|
||||||
|
|
||||||
|
|
||||||
def test_single_node():
|
def test_xml_xml_single_node():
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -40,7 +40,7 @@ def test_single_node():
|
||||||
assert obj.item == 'bar'
|
assert obj.item == 'bar'
|
||||||
|
|
||||||
|
|
||||||
def test_nested_sequence():
|
def test_xml_nested_sequence():
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -86,7 +86,37 @@ def test_nested_sequence():
|
||||||
assert obj.item.y == 2
|
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("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<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']
|
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("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -151,7 +181,7 @@ def test_single_node_no_iterable():
|
||||||
render_node(container_elm, obj)
|
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
|
# see https://github.com/mvantellingen/python-zeep/issues/252
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
|
@ -231,3 +261,37 @@ def test_complex_any_types():
|
||||||
</document>
|
</document>
|
||||||
""") # noqa
|
""") # noqa
|
||||||
assert_nodes_equal(result, expected)
|
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
|
||||||
|
|
|
@ -123,7 +123,8 @@ def test_complex_content_with_recursive_elements():
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
"""))
|
"""))
|
||||||
pet_type = schema.get_element('{http://tests.python-zeep.org/}Pet')
|
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(
|
obj = pet_type(
|
||||||
name='foo', common_name='bar',
|
name='foo', common_name='bar',
|
||||||
|
@ -602,3 +603,100 @@ def test_extension_abstract_complex_type():
|
||||||
</document>
|
</document>
|
||||||
"""
|
"""
|
||||||
assert_nodes_equal(expected, node)
|
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()
|
||||||
|
|
|
@ -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'
|
|
@ -1,9 +1,10 @@
|
||||||
|
from collections import deque
|
||||||
import pytest
|
import pytest
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from tests.utils import assert_nodes_equal, load_xml, render_node
|
from tests.utils import assert_nodes_equal, load_xml, render_node
|
||||||
from zeep import xsd
|
from zeep import xsd
|
||||||
from zeep.exceptions import XMLParseError
|
from zeep.exceptions import XMLParseError, ValidationError
|
||||||
from zeep.helpers import serialize_object
|
from zeep.helpers import serialize_object
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ def test_choice_element():
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
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_1 == 'foo'
|
||||||
assert value.item_2 is None
|
assert value.item_2 is None
|
||||||
assert value.item_3 is None
|
assert value.item_3 is None
|
||||||
|
@ -89,11 +90,89 @@ def test_choice_element_second_elm():
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
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_1 is None
|
||||||
assert value.item_2 == 'foo'
|
assert value.item_2 == 'foo'
|
||||||
assert value.item_3 is None
|
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():
|
def test_choice_element_multiple():
|
||||||
node = etree.fromstring("""
|
node = etree.fromstring("""
|
||||||
|
@ -137,7 +216,7 @@ def test_choice_element_multiple():
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
value = element.parse(node.getchildren()[0], schema)
|
value = element.parse(node[0], schema)
|
||||||
assert value._value_1 == [
|
assert value._value_1 == [
|
||||||
{'item_1': 'foo'}, {'item_2': 'bar'}, {'item_1': 'three'},
|
{'item_1': 'foo'}, {'item_2': 'bar'}, {'item_1': 'three'},
|
||||||
]
|
]
|
||||||
|
@ -179,6 +258,8 @@ def test_choice_element_optional():
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
value = element.parse(node[0], schema)
|
||||||
|
assert value.item_4 == 'foo'
|
||||||
|
|
||||||
|
|
||||||
def test_choice_element_with_any():
|
def test_choice_element_with_any():
|
||||||
|
@ -219,7 +300,7 @@ def test_choice_element_with_any():
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
result = element.parse(node.getchildren()[0], schema)
|
result = element.parse(node[0], schema)
|
||||||
assert result.name == 'foo'
|
assert result.name == 'foo'
|
||||||
assert result.something is True
|
assert result.something is True
|
||||||
assert result.item_1 == 'foo'
|
assert result.item_1 == 'foo'
|
||||||
|
@ -266,7 +347,7 @@ def test_choice_element_with_any_max_occurs():
|
||||||
"""
|
"""
|
||||||
node = render_node(element, value)
|
node = render_node(element, value)
|
||||||
assert_nodes_equal(node, expected)
|
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.item_2 == 'item-2'
|
||||||
assert result._value_1 == ['any-content']
|
assert result._value_1 == ['any-content']
|
||||||
|
|
||||||
|
@ -319,8 +400,8 @@ def test_choice_in_sequence():
|
||||||
schema = xsd.Schema(node)
|
schema = xsd.Schema(node)
|
||||||
container_elm = schema.get_element('ns0:container')
|
container_elm = schema.get_element('ns0:container')
|
||||||
|
|
||||||
assert container_elm.type.signature() == (
|
assert container_elm.type.signature(schema=schema) == (
|
||||||
'something: xsd:string, ({item_1: xsd:string} | {item_2: xsd:string} | {item_3: xsd:string})') # noqa
|
'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')
|
value = container_elm(something='foobar', item_1='item-1')
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
|
@ -334,6 +415,7 @@ def test_choice_in_sequence():
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
container_elm.render(node, value)
|
container_elm.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
value = container_elm.parse(node[0], schema)
|
||||||
|
|
||||||
|
|
||||||
def test_choice_with_sequence():
|
def test_choice_with_sequence():
|
||||||
|
@ -362,8 +444,8 @@ def test_choice_with_sequence():
|
||||||
""")
|
""")
|
||||||
schema = xsd.Schema(node)
|
schema = xsd.Schema(node)
|
||||||
element = schema.get_element('ns0:container')
|
element = schema.get_element('ns0:container')
|
||||||
assert element.type.signature() == (
|
assert element.type.signature(schema=schema) == (
|
||||||
'({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})')
|
'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')
|
value = element(item_1='foo', item_2='bar')
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
|
@ -377,6 +459,7 @@ def test_choice_with_sequence():
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
value = element.parse(node[0], schema)
|
||||||
|
|
||||||
|
|
||||||
def test_choice_with_sequence_once():
|
def test_choice_with_sequence_once():
|
||||||
|
@ -404,8 +487,8 @@ def test_choice_with_sequence_once():
|
||||||
""")
|
""")
|
||||||
schema = xsd.Schema(node)
|
schema = xsd.Schema(node)
|
||||||
element = schema.get_element('ns0:container')
|
element = schema.get_element('ns0:container')
|
||||||
assert element.type.signature() == (
|
assert element.type.signature(schema=schema) == (
|
||||||
'item_0: xsd:string, ({item_1: xsd:string, item_2: xsd:string})')
|
'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')
|
value = element(item_0='nul', item_1='foo', item_2='bar')
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
|
@ -420,6 +503,94 @@ def test_choice_with_sequence_once():
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
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():
|
def test_choice_with_sequence_once_extra_data():
|
||||||
|
@ -448,8 +619,8 @@ def test_choice_with_sequence_once_extra_data():
|
||||||
""")
|
""")
|
||||||
schema = xsd.Schema(node)
|
schema = xsd.Schema(node)
|
||||||
element = schema.get_element('ns0:container')
|
element = schema.get_element('ns0:container')
|
||||||
assert element.type.signature() == (
|
assert element.type.signature(schema=schema) == (
|
||||||
'item_0: xsd:string, ({item_1: xsd:string, item_2: xsd:string}), item_3: xsd:string')
|
'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')
|
value = element(item_0='nul', item_1='foo', item_2='bar', item_3='item-3')
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
|
@ -465,6 +636,7 @@ def test_choice_with_sequence_once_extra_data():
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
value = element.parse(node[0], schema)
|
||||||
|
|
||||||
|
|
||||||
def test_choice_with_sequence_second():
|
def test_choice_with_sequence_second():
|
||||||
|
@ -493,8 +665,8 @@ def test_choice_with_sequence_second():
|
||||||
""")
|
""")
|
||||||
schema = xsd.Schema(node)
|
schema = xsd.Schema(node)
|
||||||
element = schema.get_element('ns0:container')
|
element = schema.get_element('ns0:container')
|
||||||
assert element.type.signature() == (
|
assert element.type.signature(schema=schema) == (
|
||||||
'({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})')
|
'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')
|
value = element(item_3='foo', item_4='bar')
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
|
@ -508,6 +680,7 @@ def test_choice_with_sequence_second():
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
value = element.parse(node[0], schema)
|
||||||
|
|
||||||
|
|
||||||
def test_choice_with_sequence_invalid():
|
def test_choice_with_sequence_invalid():
|
||||||
|
@ -536,8 +709,8 @@ def test_choice_with_sequence_invalid():
|
||||||
""")
|
""")
|
||||||
schema = xsd.Schema(node)
|
schema = xsd.Schema(node)
|
||||||
element = schema.get_element('ns0:container')
|
element = schema.get_element('ns0:container')
|
||||||
assert element.type.signature() == (
|
assert element.type.signature(schema=schema) == (
|
||||||
'({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})')
|
'ns0:container(({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string}))')
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
element(item_1='foo', item_4='bar')
|
element(item_1='foo', item_4='bar')
|
||||||
|
@ -594,6 +767,7 @@ def test_choice_with_sequence_change():
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
element.render(node, elm)
|
element.render(node, elm)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
value = element.parse(node[0], schema)
|
||||||
|
|
||||||
|
|
||||||
def test_choice_with_sequence_change_named():
|
def test_choice_with_sequence_change_named():
|
||||||
|
@ -638,6 +812,7 @@ def test_choice_with_sequence_change_named():
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
element.render(node, elm)
|
element.render(node, elm)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
value = element.parse(node[0], schema)
|
||||||
|
|
||||||
|
|
||||||
def test_choice_with_sequence_multiple():
|
def test_choice_with_sequence_multiple():
|
||||||
|
@ -666,8 +841,8 @@ def test_choice_with_sequence_multiple():
|
||||||
""")
|
""")
|
||||||
schema = xsd.Schema(node)
|
schema = xsd.Schema(node)
|
||||||
element = schema.get_element('ns0:container')
|
element = schema.get_element('ns0:container')
|
||||||
assert element.type.signature() == (
|
assert element.type.signature(schema=schema) == (
|
||||||
'({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})[]')
|
'ns0:container(({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})[])')
|
||||||
value = element(_value_1=[
|
value = element(_value_1=[
|
||||||
dict(item_1='foo', item_2='bar'),
|
dict(item_1='foo', item_2='bar'),
|
||||||
dict(item_3='foo', item_4='bar'),
|
dict(item_3='foo', item_4='bar'),
|
||||||
|
@ -686,6 +861,7 @@ def test_choice_with_sequence_multiple():
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
value = element.parse(node[0], schema)
|
||||||
|
|
||||||
|
|
||||||
def test_choice_with_sequence_and_element():
|
def test_choice_with_sequence_and_element():
|
||||||
|
@ -713,8 +889,8 @@ def test_choice_with_sequence_and_element():
|
||||||
""")
|
""")
|
||||||
schema = xsd.Schema(node)
|
schema = xsd.Schema(node)
|
||||||
element = schema.get_element('ns0:container')
|
element = schema.get_element('ns0:container')
|
||||||
assert element.type.signature() == (
|
assert element.type.signature(schema=schema) == (
|
||||||
'({item_1: xsd:string} | {({item_2: xsd:string} | {item_3: xsd:string})})')
|
'ns0:container(({item_1: xsd:string} | {({item_2: xsd:string} | {item_3: xsd:string})}))')
|
||||||
|
|
||||||
value = element(item_2='foo')
|
value = element(item_2='foo')
|
||||||
|
|
||||||
|
@ -728,6 +904,7 @@ def test_choice_with_sequence_and_element():
|
||||||
node = etree.Element('document')
|
node = etree.Element('document')
|
||||||
element.render(node, value)
|
element.render(node, value)
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
value = element.parse(node[0], schema)
|
||||||
|
|
||||||
|
|
||||||
def test_element_ref_in_choice():
|
def test_element_ref_in_choice():
|
||||||
|
@ -1043,3 +1220,138 @@ def test_choice_extend():
|
||||||
assert value['item-1-2'] == 'bar'
|
assert value['item-1-2'] == 'bar'
|
||||||
assert value['_value_1'][0] == {'item-2-1': 'xafoo'}
|
assert value['_value_1'][0] == {'item-2-1': 'xafoo'}
|
||||||
assert value['_value_1'][1] == {'item-2-2': 'xabar'}
|
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
|
|
@ -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
|
|
@ -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'
|
|
@ -3,11 +3,11 @@ import copy
|
||||||
import pytest
|
import pytest
|
||||||
from lxml import etree
|
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
|
from zeep import xsd
|
||||||
|
|
||||||
|
|
||||||
def test_complex_type_nested_wrong_type():
|
def test_xml_complex_type_nested_wrong_type():
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -36,7 +36,7 @@ def test_complex_type_nested_wrong_type():
|
||||||
container_elm(item={'bar': 1})
|
container_elm(item={'bar': 1})
|
||||||
|
|
||||||
|
|
||||||
def test_element_with_annotation():
|
def test_xml_element_with_annotation():
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -61,7 +61,7 @@ def test_element_with_annotation():
|
||||||
address_type(foo='bar')
|
address_type(foo='bar')
|
||||||
|
|
||||||
|
|
||||||
def test_complex_type_parsexml():
|
def test_xml_complex_type_parsexml():
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -90,8 +90,8 @@ def test_complex_type_parsexml():
|
||||||
assert obj.foo == 'bar'
|
assert obj.foo == 'bar'
|
||||||
|
|
||||||
|
|
||||||
def test_array():
|
def test_xml_array():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -105,9 +105,7 @@ def test_array():
|
||||||
</complexType>
|
</complexType>
|
||||||
</element>
|
</element>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
schema.set_ns_prefix('tns', 'http://tests.python-zeep.org/')
|
schema.set_ns_prefix('tns', 'http://tests.python-zeep.org/')
|
||||||
|
|
||||||
address_type = schema.get_element('tns:Address')
|
address_type = schema.get_element('tns:Address')
|
||||||
|
@ -130,8 +128,8 @@ def test_array():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
def test_complex_type_unbounded_one():
|
def test_xml_complex_type_unbounded_one():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -145,9 +143,7 @@ def test_complex_type_unbounded_one():
|
||||||
</complexType>
|
</complexType>
|
||||||
</element>
|
</element>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
||||||
obj = address_type(foo=['foo'])
|
obj = address_type(foo=['foo'])
|
||||||
|
|
||||||
|
@ -164,8 +160,8 @@ def test_complex_type_unbounded_one():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
def test_complex_type_unbounded_named():
|
def test_xml_complex_type_unbounded_named():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -178,9 +174,8 @@ def test_complex_type_unbounded_named():
|
||||||
</sequence>
|
</sequence>
|
||||||
</complexType>
|
</complexType>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
||||||
obj = address_type()
|
obj = address_type()
|
||||||
assert obj.foo == []
|
assert obj.foo == []
|
||||||
|
@ -201,8 +196,8 @@ def test_complex_type_unbounded_named():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
def test_complex_type_array_to_other_complex_object():
|
def test_xml_complex_type_array_to_other_complex_object():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="Address">
|
<xs:complexType name="Address">
|
||||||
|
@ -217,9 +212,8 @@ def test_complex_type_array_to_other_complex_object():
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:element name="ArrayOfAddress" type="ArrayOfAddress"/>
|
<xs:element name="ArrayOfAddress" type="ArrayOfAddress"/>
|
||||||
</xs:schema>
|
</xs:schema>
|
||||||
""".strip()) # noqa
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
address_array = schema.get_element('ArrayOfAddress')
|
address_array = schema.get_element('ArrayOfAddress')
|
||||||
obj = address_array()
|
obj = address_array()
|
||||||
assert obj.Address == []
|
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='foo'))
|
||||||
obj.Address.append(schema.get_type('Address')(foo='bar'))
|
obj.Address.append(schema.get_type('Address')(foo='bar'))
|
||||||
|
|
||||||
node = etree.fromstring("""
|
expected = """
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<ArrayOfAddress>
|
<document>
|
||||||
<Address>
|
<ArrayOfAddress>
|
||||||
<foo>foo</foo>
|
<Address>
|
||||||
</Address>
|
<foo>foo</foo>
|
||||||
<Address>
|
</Address>
|
||||||
<foo>bar</foo>
|
<Address>
|
||||||
</Address>
|
<foo>bar</foo>
|
||||||
</ArrayOfAddress>
|
</Address>
|
||||||
""".strip())
|
</ArrayOfAddress>
|
||||||
|
</document>
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = render_node(address_array, obj)
|
||||||
|
assert_nodes_equal(expected, result)
|
||||||
|
|
||||||
|
|
||||||
def test_complex_type_init_kwargs():
|
def test_xml_complex_type_init_kwargs():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -256,9 +255,8 @@ def test_complex_type_init_kwargs():
|
||||||
</complexType>
|
</complexType>
|
||||||
</element>
|
</element>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
||||||
obj = address_type(
|
obj = address_type(
|
||||||
NameFirst='John', NameLast='Doe', Email='j.doe@example.com')
|
NameFirst='John', NameLast='Doe', Email='j.doe@example.com')
|
||||||
|
@ -267,8 +265,8 @@ def test_complex_type_init_kwargs():
|
||||||
assert obj.Email == 'j.doe@example.com'
|
assert obj.Email == 'j.doe@example.com'
|
||||||
|
|
||||||
|
|
||||||
def test_complex_type_init_args():
|
def test_xml_complex_type_init_args():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -283,9 +281,7 @@ def test_complex_type_init_args():
|
||||||
</complexType>
|
</complexType>
|
||||||
</element>
|
</element>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
||||||
obj = address_type('John', 'Doe', 'j.doe@example.com')
|
obj = address_type('John', 'Doe', 'j.doe@example.com')
|
||||||
assert obj.NameFirst == 'John'
|
assert obj.NameFirst == 'John'
|
||||||
|
@ -293,107 +289,9 @@ def test_complex_type_init_args():
|
||||||
assert obj.Email == 'j.doe@example.com'
|
assert obj.Email == 'j.doe@example.com'
|
||||||
|
|
||||||
|
|
||||||
def test_group():
|
def test_xml_element_ref_missing_namespace():
|
||||||
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():
|
|
||||||
# For buggy soap servers (#170)
|
# For buggy soap servers (#170)
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -407,9 +305,7 @@ def test_element_ref_missing_namespace():
|
||||||
</complexType>
|
</complexType>
|
||||||
</element>
|
</element>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
|
|
||||||
custom_type = schema.get_element('{http://tests.python-zeep.org/}bar')
|
custom_type = schema.get_element('{http://tests.python-zeep.org/}bar')
|
||||||
input_xml = load_xml("""
|
input_xml = load_xml("""
|
||||||
|
@ -421,8 +317,8 @@ def test_element_ref_missing_namespace():
|
||||||
assert item.foo == 'bar'
|
assert item.foo == 'bar'
|
||||||
|
|
||||||
|
|
||||||
def test_element_ref():
|
def test_xml_element_ref():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -437,9 +333,7 @@ def test_element_ref():
|
||||||
</complexType>
|
</complexType>
|
||||||
</element>
|
</element>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
|
|
||||||
foo_type = schema.get_element('{http://tests.python-zeep.org/}foo')
|
foo_type = schema.get_element('{http://tests.python-zeep.org/}foo')
|
||||||
assert isinstance(foo_type.type, xsd.String)
|
assert isinstance(foo_type.type, xsd.String)
|
||||||
|
@ -460,8 +354,8 @@ def test_element_ref():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
def test_element_ref_occurs():
|
def test_xml_element_ref_occurs():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -477,9 +371,7 @@ def test_element_ref_occurs():
|
||||||
</complexType>
|
</complexType>
|
||||||
</element>
|
</element>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
|
|
||||||
foo_type = schema.get_element('{http://tests.python-zeep.org/}foo')
|
foo_type = schema.get_element('{http://tests.python-zeep.org/}foo')
|
||||||
assert isinstance(foo_type.type, xsd.String)
|
assert isinstance(foo_type.type, xsd.String)
|
||||||
|
@ -500,8 +392,8 @@ def test_element_ref_occurs():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
def test_unqualified():
|
def test_xml_unqualified():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -517,9 +409,8 @@ def test_unqualified():
|
||||||
</complexType>
|
</complexType>
|
||||||
</element>
|
</element>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
||||||
obj = address_type(foo='bar')
|
obj = address_type(foo='bar')
|
||||||
|
|
||||||
|
@ -536,8 +427,8 @@ def test_unqualified():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
def test_defaults():
|
def test_xml_defaults():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xsd:schema
|
<xsd:schema
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -547,25 +438,40 @@ def test_defaults():
|
||||||
<xsd:element name="container">
|
<xsd:element name="container">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:sequence>
|
||||||
<xsd:attribute name="bar" type="xsd:string" default="hoi"/>
|
<xsd:attribute name="attr_1" type="xsd:string" default="hoi"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
container_type = schema.get_element(
|
container_type = schema.get_element(
|
||||||
'{http://tests.python-zeep.org/}container')
|
'{http://tests.python-zeep.org/}container')
|
||||||
obj = container_type()
|
obj = container_type()
|
||||||
assert obj.foo == "hoi"
|
assert obj.item_1 == "hoi"
|
||||||
assert obj.bar == "hoi"
|
assert obj.item_2 is None
|
||||||
|
assert obj.attr_1 == "hoi"
|
||||||
|
|
||||||
expected = """
|
expected = """
|
||||||
<document>
|
<document>
|
||||||
<ns0:container xmlns:ns0="http://tests.python-zeep.org/" bar="hoi">
|
<ns0:container xmlns:ns0="http://tests.python-zeep.org/" attr_1="hoi">
|
||||||
<ns0:foo>hoi</ns0:foo>
|
<ns0:item_1>hoi</ns0:item_1>
|
||||||
|
</ns0:container>
|
||||||
|
</document>
|
||||||
|
"""
|
||||||
|
node = etree.Element('document')
|
||||||
|
container_type.render(node, obj)
|
||||||
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
obj.item_2 = 'ok'
|
||||||
|
|
||||||
|
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>
|
</ns0:container>
|
||||||
</document>
|
</document>
|
||||||
"""
|
"""
|
||||||
|
@ -574,8 +480,8 @@ def test_defaults():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
def test_defaults_parse():
|
def test_xml_defaults_parse_boolean():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xsd:schema
|
<xsd:schema
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -585,29 +491,65 @@ def test_defaults_parse():
|
||||||
<xsd:element name="container">
|
<xsd:element name="container">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:sequence>
|
||||||
<xsd:attribute name="bar" type="xsd:string" default="hoi"/>
|
<xsd:attribute name="bar" type="xsd:boolean" default="0"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
</xsd:schema>
|
</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(
|
container_elm = schema.get_element(
|
||||||
'{http://tests.python-zeep.org/}container')
|
'{http://tests.python-zeep.org/}container')
|
||||||
|
|
||||||
node = load_xml("""
|
node = load_xml("""
|
||||||
<ns0:container xmlns:ns0="http://tests.python-zeep.org/">
|
<ns0:container xmlns:ns0="http://tests.python-zeep.org/">
|
||||||
<ns0:foo>hoi</ns0:foo>
|
<ns0:item_1>hoi</ns0:item_1>
|
||||||
</ns0:container>
|
</ns0:container>
|
||||||
""")
|
""")
|
||||||
item = container_elm.parse(node, schema)
|
item = container_elm.parse(node, schema)
|
||||||
assert item.bar == 'hoi'
|
assert item.attr_1 == 'hoi'
|
||||||
|
|
||||||
|
|
||||||
def test_init_with_dicts():
|
def test_xml_init_with_dicts():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xsd:schema
|
<xsd:schema
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -638,9 +580,8 @@ def test_init_with_dicts():
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
||||||
obj = address_type(name='foo', container={'service': [{'name': 'foo'}]})
|
obj = address_type(name='foo', container={'service': [{'name': 'foo'}]})
|
||||||
|
|
||||||
|
@ -662,9 +603,8 @@ def test_init_with_dicts():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
|
def test_xml_sequence_in_sequence():
|
||||||
def test_sequence_in_sequence():
|
schema = xsd.Schema(load_xml("""
|
||||||
node = load_xml("""
|
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema
|
<schema
|
||||||
xmlns="http://www.w3.org/2001/XMLSchema"
|
xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -684,8 +624,7 @@ def test_sequence_in_sequence():
|
||||||
</element>
|
</element>
|
||||||
<element name="foobar" type="xsd:string"/>
|
<element name="foobar" type="xsd:string"/>
|
||||||
</schema>
|
</schema>
|
||||||
""")
|
"""))
|
||||||
schema = xsd.Schema(node)
|
|
||||||
element = schema.get_element('ns0:container')
|
element = schema.get_element('ns0:container')
|
||||||
value = element(item_1="foo", item_2="bar")
|
value = element(item_1="foo", item_2="bar")
|
||||||
|
|
||||||
|
@ -703,7 +642,7 @@ def test_sequence_in_sequence():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
def test_sequence_in_sequence_many():
|
def test_xml_sequence_in_sequence_many():
|
||||||
node = load_xml("""
|
node = load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema
|
<schema
|
||||||
|
@ -753,8 +692,8 @@ def test_sequence_in_sequence_many():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
def test_complex_type_empty():
|
def test_xml_complex_type_empty():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -769,9 +708,7 @@ def test_complex_type_empty():
|
||||||
</complexType>
|
</complexType>
|
||||||
</element>
|
</element>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
|
|
||||||
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
|
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
|
||||||
obj = container_elm(something={})
|
obj = container_elm(something={})
|
||||||
|
@ -790,7 +727,7 @@ def test_complex_type_empty():
|
||||||
assert item.something is None
|
assert item.something is None
|
||||||
|
|
||||||
|
|
||||||
def test_schema_as_payload():
|
def test_xml_schema_as_payload():
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -834,7 +771,7 @@ def test_schema_as_payload():
|
||||||
assert value._value_1['item-2'] == 'value-2'
|
assert value._value_1['item-2'] == 'value-2'
|
||||||
|
|
||||||
|
|
||||||
def test_nill():
|
def test_xml_nill():
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -868,8 +805,8 @@ def test_nill():
|
||||||
assert_nodes_equal(expected, node)
|
assert_nodes_equal(expected, node)
|
||||||
|
|
||||||
|
|
||||||
def test_empty_xmlns():
|
def test_xml_empty_xmlns():
|
||||||
node = load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
|
@ -885,9 +822,7 @@ def test_empty_xmlns():
|
||||||
</complexType>
|
</complexType>
|
||||||
</element>
|
</element>
|
||||||
</schema>
|
</schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
|
|
||||||
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
|
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
|
||||||
node = load_xml("""
|
node = load_xml("""
|
||||||
|
@ -905,8 +840,8 @@ def test_empty_xmlns():
|
||||||
assert item._value_1 == 'foo'
|
assert item._value_1 == 'foo'
|
||||||
|
|
||||||
|
|
||||||
def test_keep_objects_intact():
|
def test_xml_keep_objects_intact():
|
||||||
node = etree.fromstring("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xsd:schema
|
<xsd:schema
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
@ -937,9 +872,8 @@ def test_keep_objects_intact():
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
""".strip())
|
"""))
|
||||||
|
|
||||||
schema = xsd.Schema(node)
|
|
||||||
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
|
||||||
obj = address_type(name='foo', container={'service': [{'name': 'foo'}]})
|
obj = address_type(name='foo', container={'service': [{'name': 'foo'}]})
|
||||||
|
|
||||||
|
|
|
@ -7,132 +7,6 @@ from zeep import xsd
|
||||||
from zeep.xsd.schema import Schema
|
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():
|
def test_sequence_parse_regression():
|
||||||
schema_doc = load_xml(b"""
|
schema_doc = load_xml(b"""
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
@ -239,7 +113,7 @@ def test_sequence_parse_anytype_obj():
|
||||||
'{http://www.w3.org/2001/XMLSchema}Schema',
|
'{http://www.w3.org/2001/XMLSchema}Schema',
|
||||||
targetNamespace='http://tests.python-zeep.org/'))
|
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)
|
root.register_type('{http://tests.python-zeep.org/}something', value_type)
|
||||||
|
|
||||||
custom_type = xsd.Element(
|
custom_type = xsd.Element(
|
||||||
|
@ -264,147 +138,6 @@ def test_sequence_parse_anytype_obj():
|
||||||
assert obj.item_1.value == 100
|
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():
|
def test_sequence_parse_anytype_regression_17():
|
||||||
schema_doc = load_xml(b"""
|
schema_doc = load_xml(b"""
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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'
|
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():
|
def test_nested_complex_type():
|
||||||
custom_type = xsd.Element(
|
custom_type = xsd.Element(
|
||||||
|
@ -723,7 +285,7 @@ def test_nested_complex_type_optional():
|
||||||
""")
|
""")
|
||||||
obj = custom_type.parse(expected, None)
|
obj = custom_type.parse(expected, None)
|
||||||
assert obj.item_1 == 'foo'
|
assert obj.item_1 == 'foo'
|
||||||
assert obj.item_2 == []
|
assert obj.item_2 == [None]
|
||||||
|
|
||||||
expected = etree.fromstring("""
|
expected = etree.fromstring("""
|
||||||
<ns0:container xmlns:ns0="http://tests.python-zeep.org/">
|
<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.item_2 == datetime.date(2016, 10, 20)
|
||||||
assert result.attr_1 is None
|
assert result.attr_1 is None
|
||||||
assert result.attr_2 == datetime.date(2013, 10, 20)
|
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')
|
||||||
|
|
|
@ -4,6 +4,8 @@ from lxml import etree
|
||||||
from tests.utils import DummyTransport, load_xml
|
from tests.utils import DummyTransport, load_xml
|
||||||
from zeep import exceptions, xsd
|
from zeep import exceptions, xsd
|
||||||
from zeep.xsd import Schema
|
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():
|
def test_default_types():
|
||||||
|
@ -98,7 +100,7 @@ def test_invalid_localname_handling():
|
||||||
|
|
||||||
def test_schema_repr_none():
|
def test_schema_repr_none():
|
||||||
schema = xsd.Schema()
|
schema = xsd.Schema()
|
||||||
assert repr(schema) == "<Schema(location='<none>')>"
|
assert repr(schema) == "<Schema()>"
|
||||||
|
|
||||||
|
|
||||||
def test_schema_repr_val():
|
def test_schema_repr_val():
|
||||||
|
@ -111,7 +113,7 @@ def test_schema_repr_val():
|
||||||
elementFormDefault="qualified">
|
elementFormDefault="qualified">
|
||||||
</xs:schema>
|
</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():
|
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_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')
|
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_b.type, UnresolvedType)
|
||||||
assert not isinstance(elm_c.type, xsd.UnresolvedType)
|
assert not isinstance(elm_c.type, UnresolvedType)
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_no_namespace():
|
def test_multiple_no_namespace():
|
||||||
|
@ -644,6 +646,136 @@ def test_include_recursion():
|
||||||
schema.get_element('{http://tests.python-zeep.org/b}bar')
|
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():
|
def test_merge():
|
||||||
node_a = etree.fromstring("""
|
node_a = etree.fromstring("""
|
||||||
<?xml version="1.0"?>
|
<?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/a}foo')
|
||||||
schema_a.get_element('{http://tests.python-zeep.org/b}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)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from zeep import xsd
|
from zeep import xsd
|
||||||
|
from tests.utils import load_xml
|
||||||
|
|
||||||
|
|
||||||
def test_signature_complex_type_choice():
|
def test_signature_complex_type_choice():
|
||||||
|
@ -16,7 +17,7 @@ def test_signature_complex_type_choice():
|
||||||
xsd.String()),
|
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():
|
def test_signature_complex_type_choice_sequence():
|
||||||
|
@ -38,7 +39,7 @@ def test_signature_complex_type_choice_sequence():
|
||||||
])
|
])
|
||||||
))
|
))
|
||||||
assert custom_type.signature() == (
|
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():
|
def test_signature_nested_sequences():
|
||||||
|
@ -80,7 +81,7 @@ def test_signature_nested_sequences():
|
||||||
))
|
))
|
||||||
|
|
||||||
assert custom_type.signature() == (
|
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() == (
|
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()
|
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')
|
custom_type(item_1='foo')
|
||||||
|
|
||||||
|
|
||||||
|
@ -161,7 +162,7 @@ def test_signature_complex_type_sequence_with_any():
|
||||||
])
|
])
|
||||||
))
|
))
|
||||||
assert custom_type.signature() == (
|
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():
|
def test_signature_complex_type_sequence_with_anys():
|
||||||
|
@ -184,4 +185,30 @@ def test_signature_complex_type_sequence_with_anys():
|
||||||
])
|
])
|
||||||
))
|
))
|
||||||
assert custom_type.signature() == (
|
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)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ def test_union_same_types():
|
||||||
schema = xsd.Schema(load_xml("""
|
schema = xsd.Schema(load_xml("""
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xsd:schema
|
<xsd:schema
|
||||||
|
xmlns="http://tests.python-zeep.org/"
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:tns="http://tests.python-zeep.org/"
|
xmlns:tns="http://tests.python-zeep.org/"
|
||||||
targetNamespace="http://tests.python-zeep.org/"
|
targetNamespace="http://tests.python-zeep.org/"
|
||||||
|
@ -21,7 +22,7 @@ def test_union_same_types():
|
||||||
</xsd:simpleType>
|
</xsd:simpleType>
|
||||||
|
|
||||||
<xsd:simpleType name="Date">
|
<xsd:simpleType name="Date">
|
||||||
<xsd:union memberTypes="MMYY MMYYYY"/>
|
<xsd:union memberTypes="tns:MMYY MMYYYY"/>
|
||||||
</xsd:simpleType>
|
</xsd:simpleType>
|
||||||
<xsd:element name="item" type="tns:Date"/>
|
<xsd:element name="item" type="tns:Date"/>
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
|
@ -49,7 +50,7 @@ def test_union_mixed():
|
||||||
elementFormDefault="qualified">
|
elementFormDefault="qualified">
|
||||||
<xsd:element name="item" type="tns:Date"/>
|
<xsd:element name="item" type="tns:Date"/>
|
||||||
<xsd:simpleType name="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>
|
||||||
<xsd:simpleType name="MMYY">
|
<xsd:simpleType name="MMYY">
|
||||||
<xsd:restriction base="xsd:string">
|
<xsd:restriction base="xsd:string">
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
import json
|
||||||
|
import pickle
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import six
|
import six
|
||||||
|
from lxml.etree import QName
|
||||||
|
|
||||||
from zeep import xsd
|
from zeep import xsd
|
||||||
from zeep.xsd import valueobjects
|
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()),
|
||||||
]),
|
]),
|
||||||
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
|
assert xsd_type.signature() == expected
|
||||||
|
|
||||||
args = tuple([])
|
args = tuple([])
|
||||||
|
@ -401,3 +406,35 @@ def test_choice_sequences_init_dict():
|
||||||
{'item_1': 'value-1', 'item_2': 'value-2'}
|
{'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',
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ def test_schema_empty():
|
||||||
</schema>
|
</schema>
|
||||||
""")
|
""")
|
||||||
schema = parse_schema_node(node)
|
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._element_form == 'qualified'
|
||||||
assert root._attribute_form == 'unqualified'
|
assert root._attribute_form == 'unqualified'
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<definitions
|
<definitions
|
||||||
xmlns:tns="http://example.com/stockquote.wsdl"
|
xmlns:tns="http://example.com/stockquote.wsdl"
|
||||||
xmlns:xsd1="http://example.com/stockquote.xsd"
|
xmlns:xsd1="http://example.com/stockquote.xsd"
|
||||||
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
||||||
xmlns="http://schemas.xmlsoap.org/wsdl/"
|
xmlns="http://schemas.xmlsoap.org/wsdl/"
|
||||||
name="StockQuote"
|
name="StockQuote"
|
||||||
targetNamespace="http://example.com/stockquote.wsdl">
|
targetNamespace="http://example.com/stockquote.wsdl">
|
||||||
<types>
|
<types>
|
||||||
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
||||||
targetNamespace="http://example.com/stockquote.xsd"
|
targetNamespace="http://example.com/stockquote.xsd"
|
||||||
xmlns:tns="http://example.com/stockquote.xsd" >
|
xmlns:tns="http://example.com/stockquote.xsd" >
|
||||||
<complexType name="Address">
|
<complexType name="Address">
|
||||||
|
@ -17,6 +17,12 @@
|
||||||
<element minOccurs="0" maxOccurs="1" name="Email" type="string"/>
|
<element minOccurs="0" maxOccurs="1" name="Email" type="string"/>
|
||||||
</sequence>
|
</sequence>
|
||||||
</complexType>
|
</complexType>
|
||||||
|
<complexType name="ArrayOfAddress">
|
||||||
|
<sequence>
|
||||||
|
<element name="Address" type="tns:Address" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
</sequence>
|
||||||
|
</complexType>
|
||||||
|
|
||||||
<element name="Fault1">
|
<element name="Fault1">
|
||||||
<complexType>
|
<complexType>
|
||||||
<sequence>
|
<sequence>
|
||||||
|
@ -87,6 +93,9 @@
|
||||||
<fault message="tns:FaultMessageMsg1" name="fault1"/>
|
<fault message="tns:FaultMessageMsg1" name="fault1"/>
|
||||||
<fault message="tns:FaultMessageMsg2" name="fault2"/>
|
<fault message="tns:FaultMessageMsg2" name="fault2"/>
|
||||||
</operation>
|
</operation>
|
||||||
|
<operation name="GetLastTradePriceNoOutput">
|
||||||
|
<input message="tns:GetLastTradePriceInput"/>
|
||||||
|
</operation>
|
||||||
</portType>
|
</portType>
|
||||||
<binding name="StockQuoteBinding" type="tns:StockQuotePortType">
|
<binding name="StockQuoteBinding" type="tns:StockQuotePortType">
|
||||||
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
|
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
|
||||||
|
@ -105,6 +114,12 @@
|
||||||
<soap:fault name="fault2" use="literal"/>
|
<soap:fault name="fault2" use="literal"/>
|
||||||
</fault>
|
</fault>
|
||||||
</operation>
|
</operation>
|
||||||
|
<operation name="GetLastTradePriceNoOutput">
|
||||||
|
<soap:operation soapAction="http://example.com/GetLastTradePrice"/>
|
||||||
|
<input>
|
||||||
|
<soap:body use="literal"/>
|
||||||
|
</input>
|
||||||
|
</operation>
|
||||||
</binding>
|
</binding>
|
||||||
<service name="StockQuoteService">
|
<service name="StockQuoteService">
|
||||||
<documentation>My first service</documentation>
|
<documentation>My first service</documentation>
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
<?xml version="1.0"?>
|
<?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>
|
<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">
|
<complexType name="Address">
|
||||||
<sequence>
|
<sequence>
|
||||||
<element minOccurs="0" maxOccurs="1" name="NameFirst" type="string"/>
|
<element minOccurs="0" maxOccurs="1" name="NameFirst" type="string"/>
|
||||||
|
@ -48,6 +57,10 @@
|
||||||
<message name="GetLastTradePriceOutput">
|
<message name="GetLastTradePriceOutput">
|
||||||
<part name="body" element="xsd1:TradePrice"/>
|
<part name="body" element="xsd1:TradePrice"/>
|
||||||
</message>
|
</message>
|
||||||
|
<message name="OptionalResponseHeader">
|
||||||
|
<part name="body" type="xsd:string"/>
|
||||||
|
</message>
|
||||||
|
|
||||||
<portType name="StockQuotePortType">
|
<portType name="StockQuotePortType">
|
||||||
<operation name="GetLastTradePrice">
|
<operation name="GetLastTradePrice">
|
||||||
<input message="tns:GetLastTradePriceInput"/>
|
<input message="tns:GetLastTradePriceInput"/>
|
||||||
|
@ -65,6 +78,7 @@
|
||||||
</input>
|
</input>
|
||||||
<output>
|
<output>
|
||||||
<soap:body use="literal"/>
|
<soap:body use="literal"/>
|
||||||
|
<soap:header message="tns:OptionalResponseHeader" part="body" use="literal"/>
|
||||||
</output>
|
</output>
|
||||||
</operation>
|
</operation>
|
||||||
</binding>
|
</binding>
|
||||||
|
|
Loading…
Reference in New Issue