python suds wrong namespace prefix in SOAP request - python

I use python/suds to implement a client and I get wrong namespace prefixes in the sent SOAP header for a spefic type of parameters defined by element ref= in the wsdl.
The .wsdl is referencing a data types .xsd file, see below. The issue is with the function GetRecordAttributes and its first argument of type gbt:recordReferences.
File: browse2.wsdl
<xsd:schema targetNamespace="http://www.grantadesign.com/10/10/Browse" xmlns="http://www.grantadesign.com/10/10/Browse" xmlns:gbt="http://www.grantadesign.com/10/10/GrantaBaseTypes" elementFormDefault="qualified" attributeFormDefault="qualified">
<xsd:import schemaLocation="grantabasetypes2.xsd" namespace="http://www.grantadesign.com/10/10/GrantaBaseTypes"/>
<xsd:element name="GetRecordAttributes">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="gbt:recordReferences">
</xsd:element>
Referenced File : grantabasetypes2.xsd
<element name="recordReferences">
<complexType>
<sequence>
<element name="record" minOccurs="0" maxOccurs="unbounded" type="gbt:MIRecordReference"/>
</sequence>
</complexType>
</element>
SOAP Request sent by suds:
<SOAP-ENV:Envelope xmlns:ns0="http://www.grantadesign.com/10/10/GrantaBaseTypes" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://www.grantadesign.com/10/10/Browse" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<ns1:Body>
<ns2:GetRecordAttributes>
<ns2:recordReferences>
<ns0:record>
</ns0:record>
</ns2:recordReferences>
</ns2:GetRecordAttributes>
</ns1:Body>
</SOAP-ENV:Envelope>
Problem : <ns2:recordReferences> has wrong prefix, should be <ns0:recordReferences> since it belongs to the namespace ...GrantaBaseTypes defined in the .xsd.
This happens for all arguments defined by ref= in the wsdl. How can this be automatically fixed?
Note: I checked that the "good" prefix is accepted by the service by manually sending the xml SOAP request via curl.
UPDATE
I meddled with SUDS source code and the following empirical fix forces all elements with ref= attribute to assume the ref-ed namespace (previously, they take on the schema root namespace or whatever tns is):
File: /suds/xsd/sxbase.py
class SchemaObject(object):
....
def namespace(self, prefix=None):
ns = self.schema.tns
#FIX BEGIN
if self.ref and self.ref in self.schema.elements.keys():
ns = self.ref
#FIX END
Works with my service, but I'm not sure if it'll break other things. I would prefer a smarter solution that does not change SUDS source code.
Thanks,
Alex

Write a Suds plugin to modify the XML before it is sent.
from suds.client import Client
from suds.plugin import MessagePlugin
class MyPlugin(MessagePlugin):
def marshalled(self, context):
#modify this line to reliably find the "recordReferences" element
context.envelope[1][0][0].setPrefix('ns0')
client = Client(WSDL_URL, plugins=[MyPlugin()])
Quoting Suds documentation:
marshalled()
Provides the plugin with the opportunity to inspect/modify the envelope Document before it is sent.

I had the exact same problem when using suds to access a BizTalk/IIS SOAP service.
From what I can tell from the WSDL it occurs when there is a "complexType" that is not part of the "targetNamespace" (it has it's own), which has a child that is also a complexType, but with no namespace set. In BizTalk this means that the child should belong to the same namespace as the parent, but Suds seem to think that it then should be part of the targetNamespace ....
The fix in the source-code solved the thing "correctly", but since I want to be able to upgrade without applying the fix every time I went for another solution....
My solution was to skip Suds and just copy the raw XML, use that as a template and copy the values into it ... Not beautiful, but at least simple.
The solution to add a plugin is in my opinion equally hardcoded and perhaps even harder to maintain.

You could build soap message yourself and use SoapClient to send the message :
sc = SoapClient(cli.service.XXXMethod.client,cli.service.XXXMethod.method)
sc.send(some_soap_doc)

I prefer regular expressions :)
import re
class EnvelopeFixer(MessagePlugin):
def sending(self, context):
# rimuovi i prefissi
context.envelope = re.sub( 'ns[0-9]:', '', context.envelope )
return context.envelope

Related

Create a request with zeep that includes AnyObject types

This is basically the same question as here:
creating any object with python zeep
I am working on a Python script that includes SOAP API calls. Mostly these are working but I am having a problem with one. I have SOAPUI example working, it looks like this:
soap ui that works
<!-- language: lang-xml -->
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="Urn:ApiService" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<soapenv:Header/>
<soapenv:Body>
<urn:getAllOperationalDevices soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<in0 xsi:type="api:ResourceIdentityInfo" xmlns:api="http://api.common.configmgr.powerup.com">
<resourceKey xsi:type="soapenc:string">0a053ebeddf9e53f53e125d364010000</resourceKey>
<resourceName xsi:type="soapenc:string">Customer1</resourceName>
<resourceType xsi:type="soapenc:string">NETWORK</resourceType>
</in0>
<in1 xsi:type="fil:FilterInfo" xmlns:fil="http://filter.api.common.configmgr.powerup.com">
<filterComponents>
<FilterExpressionInfo xsi:type="ns:FilterExpressionInfo" xmlns:ns="http://filter.api.common.configmgr.powerup.com">
<attributeName>managementIpAddress</attributeName>
<value xsi:type="xsd:string">10.10.10.10</value>
<operator>==</operator>
</FilterExpressionInfo>
</filterComponents>
</in1>
</urn:getAllOperationalDevices>
</soapenv:Body>
</soapenv:Envelope>
I see in the WSDL that the filter value is
AnyType:
And I see in the zeep documentation that there is special handling for AnyType, with the example I copied in my code below:
zeep documentation example of AnyType
client = Client('http://my-entrprisy-endpoint.com')
value = xsd.AnyObject(xsd.String(), 'foobar')
client.service.submit_something(user_id=1, my_string=value)
My attempt to create this request in zeep looks like:
my python that returns error
print("API: getAllOperationalDevices")
in0 = {'resourceKey':nkey,'resourceName':nname,'resourceType':ntype}
value = xsd.AnyObject(xsd.String(),dmgmtip)
filter = {'attributeName':'managementIpAddress','value':value,'operator':'=='}
feInfo = {'FilterExpressionInfo' : [filter]}
filComp = {'filterComponents' : feInfo}
result = client.service.getAllOperationalDevices(in0,filComp)
I think this should be pretty close but I get the error below:
my python error
File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/zeep/xsd/elements/any.py", line 181, in validate
raise exceptions.ValidationError("Missing element for Any")
My attempts to use zeep's client.service.get_element and client.service.get_type are failing because the namespace is not recognized, seems like it should be 'urn:getAllOperationalDevices' but evidently it is not.
If anyone can give me a nudge in the right direction I'd appreciate it!

Spyne: request where input parameters have different namespaces

I found this question: https://mail.python.org/pipermail/soap/2013-June/001120.html
I have the same problem, and can't find an answer. Please help.
I am in the process of implementing some existing WSDL in spyne, and I'm
running into a problem where I have requests that contain multiple
namespaces. For example, I have a request that looks like:
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
</soapenv:Header>
<soapenv:Body>
<a:Foo xmlns:a="www.example.com/schema/a" AttrA="a1" AttrB="b2">
<b:Baz xmlns:b="www.example.com/schema/b" AttrC="c3"/>
<a:Bar>blah</a:Bar>
</a:Foo>
</soapenv:Body>
</soapenv:Envelope>
When I send this request, I get the following back:
<?xml version='1.0' encoding='utf-8'?>
<senv:Envelope xmlns:senv="schemas.xmlsoap.org/soap/envelope/">
<senv:Body>
<senv:Fault>
<faultcode>senv:Client.SchemaValidationError</faultcode>
<faultstring>
<string>:1:0:ERROR:SCHEMASV:SCHEMAV_ELEMENT_CONTENT:
Element '{www.example.com/schema/b}Baz': This element
is not expected. Expected is one of (
{www.example.com/schema/a}Baz,
{www.example.com/schema/a}Bar ).</faultstring>
<faultactor></faultactor>
</senv:Fault>
</senv:Body>
</senv:Envelope>
I've been looking at the documentation and setting options, and so far
nothing has gotten me past this bump. Is this currently possible with
spyne? Do I need to do more and parse the in_document? Any input would be
greatly appreciated.
An for more detail the code I've been messing with:
from spyne.model.primitive import Unicode
from spyne.model.complex import Iterable, XmlAttribute, ComplexModel,
ComplexModelMeta, ComplexModelBase
from spyne.service import ServiceBase
from spyne.protocol.soap import Soap11
from spyne.application import Application
from spyne.decorator import srpc, rpc
class BazBase(ComplexModelBase):
__namespace__ = "www.example.com/schema/b"
__metaclass__ = ComplexModelMeta
class Baz(BazBase):
Thing = Unicode
AttrC = XmlAttribute(Unicode)
class FooService(ServiceBase):
__namespace__ = "www.example.com/schema/a"
#rpc(XmlAttribute(Unicode), XmlAttribute(Unicode), Baz, Unicode,
_returns=Iterable(Unicode))
def Foo(ctx, AttrA, AttrB, Baz, Bar):
yield 'Hello, %s' % Bar
app = Application([FooService],
"www.example.com/schema/a",
in_protocol=Soap11(validator='lxml'),
out_protocol=Soap11(),
)
Thanks!
So, here's how it's supposed to work:
from spyne import Unicode, Iterable, XmlAttribute, ComplexModel, \
ServiceBase, Application, rpc
from spyne.protocol.soap import Soap11
NS_B = "www.example.com/schema/b"
class Baz(ComplexModel):
__namespace__ = NS_B
Thing = Unicode
AttrC = XmlAttribute(Unicode)
class FooCustomRequest(ComplexModel):
AttrA = XmlAttribute(Unicode)
AttrB = XmlAttribute(Unicode)
Bar = Baz.customize(sub_ns=NS_B)
Baz = Unicode
class FooService(ServiceBase):
#rpc(FooCustomRequest, _returns = Iterable(Unicode),
_body_style='bare')
def Foo(ctx, req):
AttrA, AttrB, Baz, Bar = \
req.AttrA, req.AttrB, req.Baz, req.Bar
yield 'Hello, %s' % Bar
application = Application([FooService],
tns="www.example.com/schema/a",
in_protocol=Soap11(validator='soft'),
out_protocol=Soap11(),
)
But it doesn't. This generates the following object definition:
<xs:complexType name="FooCustomRequest">
<xs:sequence>
<xs:element name="Bar" type="s0:Baz" minOccurs="0" nillable="true"/>
<xs:element name="Baz" type="xs:string" minOccurs="0"
nillable="true"/>
</xs:sequence>
<xs:attribute name="AttrA" type="xs:string"/>
<xs:attribute name="AttrB" type="xs:string"/>
</xs:complexType>
As you see, the sub_ns declaration we make above is ignored by Spyne's schema generator.
<Addendum>
My Xml is rusty but after further research this seems to be the case by design -- as the name attribute of xs:element can not have a namespace prefix (ie it's a NCName), it's not possible to model the kind of document your client is sending you using the Xml Schema technology and friends. Your best bet at this point is soft validation, if you can't persuade your client to send a "correct" request.
</Addendum>
So validator='lxml' will never accept your document. However, validator='soft' will and you can use it until this bug is fixed in Spyne.
I can confirm the following request works:
<SOAP-ENV:Envelope xmlns:b="www.example.com/schema/b"
xmlns:a="www.example.com/schema/a"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<soapenv:Body>
<a:Foo AttrA="attr-a">
<b:Bar AttrC="attr-c">
<b:Thing>thing</b:Thing>
</b:Bar>
<a:Baz>baz</a:Baz>
</a:Foo>
</soapenv:Body>
</SOAP-ENV:Envelope>
If you can file an issue at https://github.com/arskom/spyne with the XSD fragment this needs to generate, I can fix it.
<Addendum2>
I'm persuaded that a schema can only define elements in its targetNamespace. It should be possible to have immediate children of a complexType from another namespace by using an <element ref="b:Baz" /> but that's nothing more than a theory. Again, if you know the kind of schema document this needs to generate, please file an issue. Otherwise the workaround with soft validation is your best bet at the moment.
</Addendum2>

SOAP - Create an element with zeep (python)

I've to use a SOAP API for a project.
For a specific method I've to send a complex type.
This complex type is declared like that:
<complexType name="specialList">
<sequence>
<element name=data" minOccurs="0"maxOccurs="unbounded">
<complexType>
<simpleContent>
<extension base="string">
<attribute name="key" type="string" use="required"/>
</extension>
</simpleContent>
</complexType>
</element>
</sequence>
</complexType>
This is an example:
<my_action type="specialList">
<data key="myKey">MyValue</data>
<data key="myOtherKey">MyOtherValue</data>
</my_action>
To access to the SOAP API, I use zeep (I tried with suds).
The first think I do is retrieve my "specialList".
special_list = client.get_type('ns1:specialList')
my_action = special_list(data=[data_1, data_2])
However I've a problem with the type "data". Indeed this type "data" is not declared. I cannot do a client.get_type("ns1:data").
I tried several time to create an simple element but without success.
Do you have an idea how to create this "special" data ?
In advance, thank you.
Sylvain
can you try using AnyObject as indicated in their documentation: http://docs.python-zeep.org/en/master/datastructures.html
so in your code:
from zeep import xsd
special_list = client.get_type('ns1:specialList')
my_action = xsd.AnyObject(special_list, special_list(data=[data_1, data_2]))

How to validate XML with multiple namespaces in Python?

I'm trying to write some unit tests in Python 2.7 to validate against some extensions I've made to the OAI-PMH schema: http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd
The problem that I'm running into is business with multiple nested namespaces is caused by this specification in the above mentioned XSD:
<complexType name="metadataType">
<annotation>
<documentation>Metadata must be expressed in XML that complies
with another XML Schema (namespace=#other). Metadata must be
explicitly qualified in the response.</documentation>
</annotation>
<sequence>
<any namespace="##other" processContents="strict"/>
</sequence>
</complexType>
Here's a snippet of the code I'm using:
import lxml.etree, urllib2
query = "http://localhost:8080/OAI-PMH?verb=GetRecord&by_doc_ID=false&metadataPrefix=nsdl_dc&identifier=http://www.purplemath.com/modules/ratio.htm"
schema_file = file("../schemas/OAI/2.0/OAI-PMH.xsd", "r")
schema_doc = etree.parse(schema_file)
oaischema = etree.XMLSchema(schema_doc)
request = urllib2.Request(query, headers=xml_headers)
response = urllib2.urlopen(request)
body = response.read()
response_doc = etree.fromstring(body)
try:
oaischema.assertValid(response_doc)
except etree.DocumentInvalid as e:
line = 1;
for i in body.split("\n"):
print "{0}\t{1}".format(line, i)
line += 1
print(e.message)
I end up with the following error:
AssertionError: http://localhost:8080/OAI-PMH?verb=GetRecord&by_doc_ID=false&metadataPrefix=nsdl_dc&identifier=http://www.purplemath.com/modules/ratio.htm
Element '{http://www.openarchives.org/OAI/2.0/oai_dc/}oai_dc': No matching global element declaration available, but demanded by the strict wildcard., line 22
I understand the error, in that the schema is requiring that the child element of the metadata element be strictly validated, which the sample xml does.
Now I've written a validator in Java that works - however it would be helpful for this to be in Python, since the rest of the solution I'm building is Python based. To make my Java variant work, I had to make my DocumentFactory namespace aware, otherwise I got the same error. I've not found any working example in python that performs this validation correctly.
Does anyone have an idea how I can get an XML document with multiple nested namespaces as my sample doc validate with Python?
Here is the sample XML document that i'm trying to validate:
<?xml version="1.0" encoding="UTF-8"?>
<OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/
http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd">
<responseDate>2002-02-08T08:55:46Z</responseDate>
<request verb="GetRecord" identifier="oai:arXiv.org:cs/0112017"
metadataPrefix="oai_dc">http://arXiv.org/oai2</request>
<GetRecord>
<record>
<header>
<identifier>oai:arXiv.org:cs/0112017</identifier>
<datestamp>2001-12-14</datestamp>
<setSpec>cs</setSpec>
<setSpec>math</setSpec>
</header>
<metadata>
<oai_dc:dc
xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai_dc/
http://www.openarchives.org/OAI/2.0/oai_dc.xsd">
<dc:title>Using Structural Metadata to Localize Experience of
Digital Content</dc:title>
<dc:creator>Dushay, Naomi</dc:creator>
<dc:subject>Digital Libraries</dc:subject>
<dc:description>With the increasing technical sophistication of
both information consumers and providers, there is
increasing demand for more meaningful experiences of digital
information. We present a framework that separates digital
object experience, or rendering, from digital object storage
and manipulation, so the rendering can be tailored to
particular communities of users.
</dc:description>
<dc:description>Comment: 23 pages including 2 appendices,
8 figures</dc:description>
<dc:date>2001-12-14</dc:date>
</oai_dc:dc>
</metadata>
</record>
</GetRecord>
</OAI-PMH>
Found this in lxml's doc on validation:
>>> schema_root = etree.XML('''\
... <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
... <xsd:element name="a" type="xsd:integer"/>
... </xsd:schema>
... ''')
>>> schema = etree.XMLSchema(schema_root)
>>> parser = etree.XMLParser(schema = schema)
>>> root = etree.fromstring("<a>5</a>", parser)
So, perhaps, what you need is this? (See last two lines.):
schema_doc = etree.parse(schema_file)
oaischema = etree.XMLSchema(schema_doc)
request = urllib2.Request(query, headers=xml_headers)
response = urllib2.urlopen(request)
body = response.read()
parser = etree.XMLParser(schema = oaischema)
response_doc = etree.fromstring(body, parser)

How do I pass a list parameter as multiple link-named elements instead of as an array in SOAPpy?

I am trying to pass multiple instances of an element to a web servile that has the following wsdl
<complexType name="OAMCommand">
<sequence>
<element name="m-strName" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<element name="m-argVector" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
This is my code in python for the client
oamCmdStruct = SOAPpy.structType()
oamCmdStruct._addItem('m-strName','set-log-level')
oamCmdStruct._addItem('m-argVector', logLevel)
oamCmdStruct._addItem('m-argVector', loggerName)
self.serverConnection.executeCommand({'in-cmd':oamCmdStruct}
Here is the output I get from SOAPpy that gets rejected by the web service, it creates an array for the 2 m-argVector elements.
<xsd:in-cmd>
<m-strName xsi:type="xsd:string">set-log-level</m-strName>
<m-argVector SOAP-ENC:arrayType="xsd:string[2]" xsi:type="SOAP-ENC:Array">
<item>WARN_LOG_LEVEL</item>
<item>netborder</item>
</m-argVector>
</xsd:in-cmd>
Here is the output that another client sends that works. No array, just two elements that have the same name.
<SoapOAM:executeCommand>
<in-cmd>
<m-strName>set-log-level</m-strName>
<m-argVector>ERROR_LOG_LEVEL</m-argVector>
<m-argVector>netborder.media</m-argVector>
</in-cmd>
</SoapOAM:executeCommand>
How can I modify my SOAPpy code to generate the xml output like the one above ?
EDIT:
I tried the following code in python
oamCmdStruct = SOAPpy.structType( data = {"m-strName":"set-log-level",
"m-argVector": logLevel,
"m-argVector": loggerName})
But this is what the XML output from SOAPpy looked like
<xsd:in-cmd>
<m-strName xsi:type="xsd:string">set-log-level</m-strName>
<m-argVector xsi:type="xsd:string">loggerName</m-argVector>
</xsd:in-cmd>
The value for logLevel gets overwritten by loggerName instead of creating 2 entries...
After trying a few different librairies (suds, soaplib), I finally dug into the SOAPpy code.
In order to remove the arrays from my SOAP requests, I modified the dump_list() function in the SOAPBuilder class of the SOAPpy library.
# COMMENT: We dont want arrays in SOAP-XML so I commented out the following lines
# if typed:
# self.out.append(
# '<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %
# (tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl,
# self.genroot(ns_map), id, a))
#if typed:
# try: elemsname = obj._elemsname
# except: elemsname = "item"
#else:
elemsname = tag
for i in data:
self.dump(i, elemsname, not same_type, ns_map)
#if typed: self.out.append('</%s>\n' % tag)
This change gives me the following output for my SOAP XML request.
<SOAP-ENV:Body>
<ns1:executeCommand xmlns:ns1="urn:SoapOAM">
<xsd:in-cmd>
<m-strName xsi:type="xsd:string">set-log-level</m-strName>
<m-argVector>ERROR_LOG_LEVEL</m-argVector>
<m-argVector>netborder</m-argVector>
</xsd:in-cmd>
</ns1:executeCommand>
</SOAP-ENV:Body>

Categories

Resources