Spyne custom XML response - python

I am using Spyne with Django CMS.
A web service is calling my system and I want to reply with the below.
Can I use Spyne for customize response? Or do I have to go through models?
Please advise.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<tns:initTestQueryResponse xmlns:tns="http://test.com/interface/test/v2"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://test.com/interface/test/v2 testQuery.xsd"
xmlns:v21="http://test.com/model/generic-query/v2"
xmlns:v22="http://test.com/model/common/v2">
<tns:field key="ID" type="ID">
<v21:description lang="en">Identifier</v21:description>
</tns:field>
<tns:field key="CUSTOMER_NAME" type="TEXT">
<v21:description lang="en">Customer Name</v21:description>
<v21:layoutOptions bold="true" italic="false" direction="HORIZONTAL"/>
</tns:field>
<tns:section key="CUSTOMER">
<v21:description lang="en">Customer</v21:description>
</tns:section>
<tns:advancedQuery>
<tns:criteriaGroup key="CUSTOMER" operator="OR">
<v21:criterion key="ID" />
<v21:criterion key="CUSTOMER_NAME" />
</tns:criteriaGroup>
</tns:advancedQuery>
<tns:advanceQueryPerson>
<tns:criteriaGroup key="CUSTOMER" operator="OR">
<v21:criterion key="ID" />
<v21:criterion key="CUSTOMER_NAME" />
</tns:criteriaGroup>
</tns:advanceQueryPerson>
<tns:context>
<v22:status>OK</v22:status>
</tns:context>
</tns:initTestQueryResponse>
</soapenv:Body>
</soapenv:Envelope>
This the request
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<p:initTestQueryRequest xmlns:p="http://test.com/interface/test/v2"
xmlns:p1="http://test.com/model/common/v2"
xmlns:p2="http://test.com/model/generic-query/v2"
xmlns:p3="http://test.com/model/test/v2"
xmlns:p4="http://test.com/model/service-fault/v2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://test.com/interface/test/v2 ../service/test/v2/TestQuery.xsd "
xsi:type="anyType"/>
</soapenv:Body>
</soapenv:Envelope>

You have two options:
Use a bare method named initTestQuery whose return type should be a class named "initTestQueryResponse" whose namespace is "http://test.com/interface/test/v2". You need to return an initTestQueryResponse instance from the initTestQuery function.
Use a bare method named "initTestQuery" whose return type is AnyXml. You need to return an lxml.etree.Element() that contains the tags you need. Please consult the lxml documentation about how to do that.
If you want to "edit" requests after they are deserialized but before they are validated, you must subclass the protocol and override create_in_document.
class MyProtocol(Soap11):
def create_in_document(self, ctx, charset=None):
super(MyProt, self).create_in_document(ctx, charset=charset)
# Do whatever you want with ctx.in_document
app = Application(in_protocol=MyProtocol(...), ...)
I hope this helps.

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!

Parsing XML for specific item using ElementTree

I am making a request to the Salesforce merge API and getting a response like this:
xml_result = '<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com">
<soapenv:Header>
<LimitInfoHeader>
<limitInfo>
<current>62303</current>
<limit>2680000</limit><type>API REQUESTS</type></limitInfo>
</LimitInfoHeader>
</soapenv:Header>
<soapenv:Body>
<mergeResponse>
<result>
<errors>
<message>invalid record type</message>
<statusCode>INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY</statusCode>
</errors>
<id>003skdjf494244</id>
<success>false</success>
</result>
</mergeResponse>
</soapenv:Body>
</soapenv:Envelope>'
I'd like to be able to parse this response and if success=false, return the errors, statusCode, and the message text.
I've tried the following:
import xml.etree.ElementTree as ET
tree = ET.fromstring(xml_result)
root.find('mergeResponse')
root.find('{urn:partner.soap.sforce.com}mergeResponse')
root.findtext('mergeResponse')
root.findall('{urn:partner.soap.sforce.com}mergeResponse')
...and a bunch of other variations of find, findtext and findall but I can't seem to get these to return any results. Here's where I get stuck. I've tried to follow the ElementTree docs, but I don't understand how to parse the tree for specific elements.
Element.find() finds the first child with a particular tag
https://docs.python.org/2/library/xml.etree.elementtree.html#finding-interesting-elements
Since mergeResponse is a descendant, not a child, you should use XPath-syntax in this case:
root.find('.//{urn:partner.soap.sforce.com}mergeResponse')
will return your node. .// searches all descendants starting with the current node (in this case the root).

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>

Python Spyne custom output parameters

I need an output like this in Spyne:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<getActiveServicesResponse xmlns="http://mci.tajmi.ir/">
<getActiveServicesReturn>12345:2030:hafez poem:hafez </getActiveServicesReturn>
<getActiveServicesReturn>12346:2031:شعر طنز:tanz </getActiveServicesReturn>
<getActiveServicesReturn>bardari123:203861:سرویس بارداري :bar
</getActiveServicesReturn>
</getActiveServicesResponse>
</soapenv:Body>
</soapenv:Envelope>
What I can generate is
<?xml version='1.0' encoding='UTF-8'?>
<soap11env:Envelope xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://mci.tajmi.ir/">
<soap11env:Body>
<tns:getActiveServicesResponse>
<tns:getActiveServicesReturn>
<tns:string>12345:2030:hafez poem:hafez</tns:string>
<tns:string>12346:2031:شعر طنز:tanz </tns:string>
....
</tns:getActiveServicesReturn>
</tns:getActiveServicesResponse>
</soap11env:Body>
</soap11env:Envelope>
How can I customize the output? I tried complex methods without success.
have a look at my code at https://github.com/timi-ro/simulator. you can find how to make it. Also read it:
Spyne - how to duplicate one elements of wsdl file created by spyne?

python suds wrong namespace prefix in SOAP request

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

Categories

Resources