1

I trying to use the SOAP service based on "PHP SOAP server". And I have a problem with the argument passing. When it's a scalar argument, all is OK, but when I try to pass the structure there is a failure. Python libraries create arrays in the different format. In this example I'm using the SUDS but the other libraries don't makes "right format" too.

Service WSDL: http://www.drebedengi.ru/soap/dd.wsdl

PHP query:

$client = new SoapClient('http://www.drebedengi.ru/soap/dd.wsdl', array("trace" => 1));    
$client->getRecordList(
                'demo_api',
                '[email protected]',
                'demo',
                array(
                     'is_report' => false,
                     'is_show_duty' => true,
                     'r_period' => 8,
                     'r_how' => 1,
                     'r_what' => 6,
                     'r_currency' => 0,
                     'r_is_place' => 0,
                     'r_is_tag' => 0,
                )
            )
<?xml version="1.0" encoding="UTF-8"?>
    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:ddengi"
                       xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                       xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
                       SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
        <SOAP-ENV:Body>
            <ns1:getRecordList>
                <apiId xsi:type="xsd:string">demo_api</apiId>
                <login xsi:type="xsd:string">[email protected]</login>
                <pass xsi:type="xsd:string">demo</pass>
                <params xsi:type="ns2:Map">
                    <item>
                        <key xsi:type="xsd:string">is_report</key>
                        <value xsi:type="xsd:boolean">false</value>
                    </item>
                    <item>
                        <key xsi:type="xsd:string">is_show_duty</key>
                        <value xsi:type="xsd:boolean">true</value>
                    </item>
                    <item>
                        <key xsi:type="xsd:string">r_period</key>
                        <value xsi:type="xsd:int">8</value>
                    </item>
                    <item>
                        <key xsi:type="xsd:string">r_how</key>
                        <value xsi:type="xsd:int">1</value>
                    </item>
                    <item>
                        <key xsi:type="xsd:string">r_what</key>
                        <value xsi:type="xsd:int">6</value>
                    </item>
                    <item>
                        <key xsi:type="xsd:string">r_currency</key>
                        <value xsi:type="xsd:int">0</value>
                    </item>
                    <item>
                        <key xsi:type="xsd:string">r_is_place</key>
                        <value xsi:type="xsd:int">0</value>
                    </item>
                    <item>
                        <key xsi:type="xsd:string">r_is_tag</key>
                        <value xsi:type="xsd:int">0</value>
                    </item>
                </params>
                <idList xsi:nil="true" />
            </ns1:getRecordList>
        </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>

Python query with SUDS:

client = suds.client.Client("http://www.drebedengi.ru/soap/dd.wsdl")
params = {
    "is_report": False,
    "is_show_duty": True,
    "r_period": 8,
    "r_how": 1,
    "r_what": 6,
    "r_currency": 0,
    "r_is_place": 0,
    "r_is_tag": 0
}
print client.service.getRecordList("demo_api", "[email protected]", "demo", params)
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns3="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns4="urn:ddengi" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
   <SOAP-ENV:Header/>
   <ns1:Body>
      <ns4:getRecordList>
         <apiId xsi:type="ns2:string">demo_api</apiId>
         <login xsi:type="ns2:string">[email protected]</login>
         <pass xsi:type="ns2:string">demo</pass>
         <params xsi:type="ns0:params">
            <is_report xsi:type="ns2:boolean">False</is_report>
            <r_how xsi:type="ns2:int">1</r_how>
            <r_currency xsi:type="ns2:int">0</r_currency>
            <is_show_duty xsi:type="ns2:boolean">True</is_show_duty>
            <r_is_tag xsi:type="ns2:int">0</r_is_tag>
            <r_is_place xsi:type="ns2:int">0</r_is_place>
            <r_what xsi:type="ns2:int">6</r_what>
            <r_period xsi:type="ns2:int">8</r_period>
         </params>
      </ns4:getRecordList>
   </ns1:Body>
</SOAP-ENV:Envelope>

I tried to use client.factory.create() but it doesn't work: types list is empty. There is the output of print client:

Suds ( https://fedorahosted.org/suds/ )  version: 0.4 GA  build: R699-20100913

Service ( ddengiService ) tns="urn:ddengi"
   Prefixes (0)
   Ports (1):
      (SoapPort)
         Methods (28):
            deleteAll(xs:string apiId, xs:string login, xs:string pass, )
            deleteObject(xs:string apiId, xs:string login, xs:string pass, xs:integer id, xs:string type, )
            getAccessStatus(xs:string apiId, xs:string login, xs:string pass, )
            getAccumList(xs:string apiId, xs:string login, xs:string pass, xs:anyType idList, )
            getBalance(xs:string apiId, xs:string login, xs:string pass, xs:anyType params, )
            getCategoryList(xs:string apiId, xs:string login, xs:string pass, xs:anyType idList, )
            getChangeList(xs:string apiId, xs:string login, xs:string pass, xs:string revision, )
            getCurrencyList(xs:string apiId, xs:string login, xs:string pass, xs:anyType idList, )
            getCurrentRevision(xs:string apiId, xs:string login, xs:string pass, )
            getExpireDate(xs:string apiId, xs:string login, xs:string pass, )
            getOrderList(xs:string apiId, xs:string login, xs:string pass, xs:anyType idList, )
            getPlaceList(xs:string apiId, xs:string login, xs:string pass, xs:anyType idList, )
            getRecordList(xs:string apiId, xs:string login, xs:string pass, xs:anyType params, xs:anyType idList, )
            getRightAccess(xs:string apiId, xs:string login, xs:string pass, )
            getServerSubs(xs:string url, )
            getSourceList(xs:string apiId, xs:string login, xs:string pass, xs:anyType idList, )
            getSubscriptionStatus(xs:string apiId, xs:string login, xs:string pass, )
            getTagList(xs:string apiId, xs:string login, xs:string pass, xs:anyType idList, )
            getUserIdByLogin(xs:string apiId, xs:string login, xs:string pass, )
            setAccumList(xs:string apiId, xs:string login, xs:string pass, xs:string list, )
            setCategoryList(xs:string apiId, xs:string login, xs:string pass, xs:anyType list, )
            setCurrencyList(xs:string apiId, xs:string login, xs:string pass, xs:anyType list, )
            setPaymentTransaction(xs:string apiId, xs:string login, xs:string pass, xs:string transactionReceipt, xs:string amount, )
            setPlaceList(xs:string apiId, xs:string login, xs:string pass, xs:anyType list, )
            setRecordList(xs:string apiId, xs:string login, xs:string pass, xs:anyType list, )
            setSourceList(xs:string apiId, xs:string login, xs:string pass, xs:anyType list, )
            setTagList(xs:string apiId, xs:string login, xs:string pass, xs:anyType list, )
            userRegister(xs:string apiId, xs:string login, xs:string name, xs:string lang, )
         Types (0):
15
  • try to use client.factory.create() to create params. Here's code example Commented Feb 24, 2014 at 14:29
  • I forget to tell: client.factory.create() doesn't work because the types list is empty. print client shows all the methods and 0 of types. Commented Feb 24, 2014 at 17:55
  • try params = client.factory.create('{http://xml.apache.org/xml-soap}Map') Commented Feb 24, 2014 at 18:02
  • No, doesn't work. Finally I try this (doesn't work too): imp = Import('http://xml.apache.org/xml-soap') client = suds.client.Client("http://www.drebedengi.ru/soap/dd.wsdl", plugins=[ImportDoctor(imp)]) params = client.factory.create('{http://xml.apache.org/xml-soap}Map') Commented Feb 24, 2014 at 18:08
  • "doesn't work" is not very descriptive. What is the code? What output does it produce? Commented Feb 24, 2014 at 18:14

2 Answers 2

2

Here's the proper solution. Main pain points are:

  1. drebedengi.ru's WSDL does not import http://xml.apache.org/xml-soap nor http://schemas.xmlsoap.org/soap/encoding/. Those schemas are used, so they should be imported. This is fixed by ImportDoctor

  2. suds library is rather old. I will use suds-jurko 0.6 as it seems to be more up to date. On the other hand, the same approach works for suds 0.4

  3. suds knows nothing of {http://xml.apache.org/xml-soap}Map data type. Thus there's no way to instantiate that. I work around that by adding my own schema definition for xml-soap. I have no clue what it should look like, however the way I declare it works fine for me.

  4. drebedengi.ru sometimes prints ns2:... elements without declaring what ns2 actually means. Of course they mean http://xml.apache.org/xml-soap.

The first thing is to teach suds to work with Map type:

from suds.store import defaultDocumentStore
from suds.xsd.sxbasic import Import as XsdImport

defaultDocumentStore.update({'xml.apache.org/xml-soap': \
"""<?xml version="1.0" encoding="UTF-8"?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://xml.apache.org/xml-soap" targetNamespace="http://xml.apache.org/xml-soap">
      <xs:complexType name="Map">
        <xs:sequence>
          <xs:element name="item" maxOccurs="unbounded">
            <xs:complexType>
              <xs:sequence>
                 <xs:element name="key" type='xs:anyType'/>
                 <xs:element name="value" type='xs:anyType'/>
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:sequence>
      </xs:complexType>
    </xs:schema>
"""})

XsdImport.bind(
    'http://xml.apache.org/xml-soap',
    'suds://xml.apache.org/xml-soap')

Then you need to add missing imports to the WSDL:

wsdl = 'http://www.drebedengi.ru/soap/dd.wsdl'
soapenc = Import('http://schemas.xmlsoap.org/soap/encoding/')
soapenc.filter.add(None) # dd.wsdl does not have targetNamespace, thus None here
xmlsoap = Import('http://xml.apache.org/xml-soap')
xmlsoap.filter.add(None) # dd.wsdl does not have targetNamespace, thus None here
# NOTE: replace NoCache with the appropriate cache
client = Client(wsdl, cache=NoCache(), doctor=ImportDoctor(soapenc, xmlsoap))

# Sometimes responses reference ns2, so we declare it explicitly
client.add_prefix('ns2', 'http://xml.apache.org/xml-soap')
client.add_prefix('SOAP-ENC', 'http://schemas.xmlsoap.org/soap/encoding/')

Then you can instantiate that via client.factory.create(...)

raw_params = {
    "is_report": False,
    "is_show_duty": True,
    "r_period": 8,
    "r_how": 1,
    "r_what": 6,
    "r_currency": 0,
    "r_is_place": 0,
    "r_is_tag": 0
}

# Here we create the Map. Note how namespace is referenced
m = self.client.factory.create("{http://xml.apache.org/xml-soap}Map")
m['item'] = [{"key": key, "value": params[key]} for key in params]
return client.service.getRecordList("demo_api", "[email protected]", "demo", m)
Sign up to request clarification or add additional context in comments.

Comments

2

With the help of J. F. Sebastian and this answer I found the solution:

# coding=utf-8
import logging
import suds
from suds.plugin import MessagePlugin
from suds.xsd.doctor import Import, ImportDoctor


logger = logging.getLogger("suds.client")
logger.setLevel(logging.CRITICAL)
logger.addHandler(logging.StreamHandler())


class SoapFixer(MessagePlugin):
    def marshalled(self, context):
        context.envelope.nsprefixes["ns4"] = "http://xml.apache.org/xml-soap"
        context.envelope.walk(self._fix_types)
        MessagePlugin.marshalled(self, context)

    def _fix_types(self, elem):
        for attr in elem.attributes:
            if attr.name == "type" and attr.value == "ns2:Array":
                attr.setValue("ns4:Map")


imp = Import('http://schemas.xmlsoap.org/soap/encoding/')
client = suds.client.Client("http://www.drebedengi.ru/soap/dd.wsdl", doctor=ImportDoctor(imp), plugins=[SoapFixer()])

raw_params = {
    "is_report": False,
    "is_show_duty": True,
    "r_period": 8,
    "r_how": 1,
    "r_what": 6,
    "r_currency": 0,
    "r_is_place": 0,
    "r_is_tag": 0
}
array = client.factory.create("ns0:Array")
array["item"] = [{"key": key, "value": raw_params[key]} for key in raw_params]

print client.service.getRecordList("demo_api", "[email protected]", "demo", array)

2 Comments

As you say, SoapFixer is fragile. It's better to use {http://xml.apache.org/xml-soap}Map directly (see stackoverflow.com/a/41785546/1261287 )
Is it possible to do the same for Python 3? Code above doesn't work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.