spyne generates bad WSDL/XSD schema for ComplexModels with ComplexModel children - python

I'm trying to use spyne to implement a SOAP service in Python. My client sends SOAP requests like this:
<ns1:loadServices xmlns:ns1="dummy">
<serviceParams xmlns="dummy">
<header>
<user>foo</user>
<password>secret</password>
</header>
</serviceParams>
</ns1:loadServices>
But I have difficulties putting that structure into a spyne model.
So far I came up with this code:
class Header(ComplexModel):
__type_name__ = 'header'
user = Unicode
password = Unicode
class serviceParams(ComplexModel):
__type_name__ = 'serviceParams'
header = Header()
class DummyService(ServiceBase):
#rpc(serviceParams, _returns=Unicode)
def loadServices(ctx, serviceParams):
return '42'
The problem is that spyne generates and XSD like this:
...
<xs:complexType name="loadServices">
<xs:sequence>
<xs:element name="serviceParams" type="tns:serviceParams" minOccurs="0" nillable="true"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="serviceParams"/>
...
which is not what I want because essentially it says that "serviceParams" is just an empty tag without children.
Is that a bug in spyne? Or am I missing something?

It turned out that this line was the culprit:
header = Header()
that should be:
header = Header
Very nasty behavior and really easy to overlook.

Related

Why does ElementTree eat/ignore namespaces (in attribute values)?

I'm trying to read XML with ElementTree and write the result back to disk. My long-term goal is to prettify the XML this way. However, in my naive approach, ElementTree eats all the namespace declarations in the document and I don't understand why. Here is an example
test.xsd
<?xml version='1.0' encoding='UTF-8'?>
<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'
xmlns='sdformat/pose' targetNamespace='sdformat/pose'
xmlns:pose='sdformat/pose'
xmlns:types='http://sdformat.org/schemas/types.xsd'>
<xs:import namespace='sdformat/pose' schemaLocation='./pose.xsd'/>
<xs:element name='pose' type='poseType' />
<xs:simpleType name='string'><xs:restriction base='xs:string' /></xs:simpleType>
<xs:simpleType name='pose'><xs:restriction base='types:pose' /></xs:simpleType>
<xs:complexType name='poseType'>
<xs:simpleContent>
<xs:extension base="pose">
<xs:attribute name='relative_to' type='string' use='optional' default=''>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>
test.py
from xml.etree import ElementTree
ElementTree.register_namespace("types", "http://sdformat.org/schemas/types.xsd")
ElementTree.register_namespace("pose", "sdformat/pose")
ElementTree.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")
tree = ElementTree.parse("test.xsd")
tree.write("test_out.xsd")
Produces test_out.xsd
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="sdformat/pose">
<xs:import namespace="sdformat/pose" schemaLocation="./pose.xsd" />
<xs:element name="pose" type="poseType" />
<xs:simpleType name="string"><xs:restriction base="xs:string" /></xs:simpleType>
<xs:simpleType name="pose"><xs:restriction base="types:pose" /></xs:simpleType>
<xs:complexType name="poseType">
<xs:simpleContent>
<xs:extension base="pose">
<xs:attribute name="relative_to" type="string" use="optional" default="">
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>
Notice how test_out.xsd is missing any namespace declarations from test.xsd. I would expect them to be identical. I verified that the latter is valid XML by validating it. It validates with exception of my choice of namespace URI, which I think shouldn't matter.
Update:
Based on mzji's comment I realized that this only happens for values of attributes. With this in mind, I can manually add the namespaces like so:
from xml.etree import ElementTree
namespaces = {
"types": "http://sdformat.org/schemas/types.xsd",
"pose": "sdformat/pose",
"xs": "http://www.w3.org/2001/XMLSchema"
}
for prefix, ns in namespaces.items():
ElementTree.register_namespace(prefix, ns)
tree = ElementTree.parse("test.xsd")
root = tree.getroot()
queue = [tree.getroot()]
while queue:
element:ElementTree.Element = queue.pop()
for value in element.attrib.values():
try:
prefix, value = value.split(":")
except ValueError:
# no namespace, nothing to do
pass
else:
if prefix == "xs":
break # ignore XMLSchema namespace
root.attrib[f"xmlns:{prefix}"] = namespaces[prefix]
for child in element:
queue.append(child)
tree.write("test_out.xsd")
While this solves the problem, it is quite an ugly solution. I also still don't understand why this happens in the first place, so it doesn't answer the question.
There is a valid reason for this behaviour, but it requires a good understanding of XML Schema concepts.
First, some important facts:
Your XML document is not just any old XML document. It is an XSD.
An XSD is described by a schema (See schema for schema )
The attribute xs:restriction/#base is not an xs:string. Its type is xs:QName.
Based on the above facts, we can assert the following:
if test.xsd is parsed as an XML document, but without knowledge of the 'schema for schema' then the value of the base attribute will be treated as a string (technically, as PCDATA).
if test.xsd is parsed using a validating XML parser, with the 'schema for schema' as the XSD, then the value of the base attribute will be parsed as xs:QName
When ElementTree writes the output XML, its behaviour should depend on the data type of base. If base is a QName then ElementTree should detect that it is using the namespace prefix 'types' and it should emit the corresponding namespace declaration.
If you are not supplying the 'schema for schema' when parsing test.xsd then ElementTree is off the hook, because it cannot possibly know that base is supposed to be interpreted as a QName.

How to specify type when using zeep

WSDL defines an element as follows
<xs:element minOccurs="0" name="address" nillable="true" type="q146:Address"/>
My zeep request is as follows
client.service.UpdateAddressDetails(address='sample#sample.com')
But I am getting
Missing element type
(UpdateAddressDetails.address.type)
From what i know i need to specify the type for this field. How can I do it,
I have come across this zeep documentation but nothing clicked
Have you tried with uppercase 'A' in your address-parameter:
client.service.UpdateAddressDetails(Address='sample#sample.com')
Use
factory = client.type_factory('q146')
address = factory.Address(address='sample#sample.com')
client.service.UpdateAddressDetails(address=address)

Building a service for a given request

I am relatively new to SOAP frameworks and have been reading through Spynes docs and trying to figure out to build a service that accepts the following request:
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://..." xmlns:xsi=http:/..." xmlns:xsd="http://...">
<SOAP-ENV:Body>
<modifyRequest returnData="everything" xmlns="urn:...">
<attr ID="..."/>
<data>
</data>
</modifyRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I have read through the docs but just have not seen enough more complex examples to figure out how to put together something to handle this. The <attr ID="..."/> tag must be processed for the ID attribute and the <data> tags contain some varying collection of additional xml. I understand its better to formally define the service but for now I was hoping to use anyXML (?) to accept whatever is in the tags. I need to accept and process the ID attribute along with its xml payload contained within the data tags.
I'd be grateful for any guidance,
Thanks.
Here's how you'd do it:
NS = 'urn:...'
class Attr(ComplexModel):
__namespace__ = NS
_type_info = [
('ID', XmlAttribute(UnsignedInteger32)),
]
class ModifyRequest(ComplexModel):
__namespace__ = NS
_type_info = [
('returnData', XmlAttribute(Unicode(values=['everything', 'something', 'anything', 'etc']))),
('attr', Attr),
('data', AnyXml),
]
class SomeService(ServiceBase):
#rpc(ModifyRequest, _body_style='bare')
def modifyRequest(ctx, request):
pass
This requires Spyne 2.11 though, _body_style='bare' in problematic in 2.10 and older.

Python SUDS - Interrogating the WSDL for MinOccurs and MaxOccurs values

I would like to interrogate a WSDL using SUDS to get the parameters and attributes of a web service. I'm pretty much down to this one last thing. How do I interrogate the service to find the minOccurs and maxOccurs values of the parameters?
I see there's a property in the suds.xsd.sxbase object called required, but, assuming my starting point is the client object, I don't see path to get to it.
http://jortel.fedorapeople.org/suds/doc/suds.xsd.sxbase-pysrc.html#SchemaObject.required
client = Client(endpoint, username=username, password=password)
client.service[0][method]
How can I find out if a parameter is bound?
Thanks!
you can query the factory resolver for the method, and use the children() method to see its parameters.
example, for this method I have my wsdl:
<complexType name="AddAuthorizationRoleRequestType">
<sequence>
<element name="_this" type="vim25:ManagedObjectReference" />
<element name="name" type="xsd:string" />
<element name="privIds" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</sequence>
</complexType>
I can get the attributes via:
>>> a=client.factory.resolver.find("ns0:AddAuthorizationRoleRequestType")
>>> priv_el=a.children()[2][0]
<Element:0x107591a10 name="privIds" type="(u'string', u'http://www.w3.org/2001/XMLSchema')" />
>>> priv_el = a.children()[2][0]
>>> priv_el.max
unbounded
>>> priv_el.min
0
not very elegant, but it works

xml validation: validating a URI type

I'm using python's lxml to validate xmls against a schema. I have a schema with an element:
<xs:element name="link-url" type="xs:anyURL"/>
and I test, for example, this (part of an) xml:
<a link-url="server/path"/>
I would like this test to FAIL because the link-url doesn't start with http://. I tried switching anyURI to anyURL but this results in an exception - it's not a valid tag.
Is this possible with lxml? is it possible at all with schema validation?
(I'm pretty sure xs:anyURL is not valid. The XML Schema standard calls it anyURI. And since link-url is an attribute, shouldn't you be using xs:attribute instead of xs:element?)
You could restrict the URIs by creating a new simpleType based on it, and put a restriction on the pattern. For example,
#!/usr/bin/env python2.6
from lxml import etree
from StringIO import StringIO
schema_doc = etree.parse(StringIO('''
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="httpURL">
<xs:restriction base="xs:anyURI">
<xs:pattern value='https?://.+'/>
<!-- accepts only http:// or https:// URIs. -->
</xs:restriction>
</xs:simpleType>
<xs:element name="a">
<xs:complexType>
<xs:attribute name="link-url" type="httpURL"/>
</xs:complexType>
</xs:element>
</xs:schema>
''')) #/
schema = etree.XMLSchema(schema_doc)
schema.assertValid(etree.parse(StringIO('<a link-url="http://sd" />')))
assert not schema(etree.parse(StringIO('<a link-url="server/path" />')))

Categories