python suds wrong namespace prefix in SOAP request
Asked Answered
S

4

7

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

Spiccato answered 18/4, 2012 at 10:5 Comment(2)
This is clearly a bug in your stack; what do you mean by "automatically"? For example, are you willing to work with a different, yet equivalent XSD that will work with your tooling? From what you say, if you would replace the ref with a locally defined element, it'll work; for all XSD cares, the generated XML will be the same. If you think about changing on the flight, like using some sort of XSLT through a proxy, that would be a different approach. I could recommend a solution that will automatically refactor your XSD to replace refs with local elements.Plagal
I actually tried moving and changing definitions, but since I'm not an expert maybe I coudn't pinpoint the right syntax, SUDS kept putting the wrong namespace there. I would also prefer not toying with these, since they are provided by an external vendor and subject to changes. See UPDATE for a non-optimal solution I found so far.Spiccato
J
8

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.

Jobye answered 19/4, 2012 at 20:37 Comment(4)
This will work ok for this specific case. However since there are a lot of such parameters at various tree positions, fixing them by hand is not optimal. I would like something like prefix ns0 to all elements with ref= , and while one could arguably parse the whole xml tree, I believe context.envelope does not retain the ref= information.Spiccato
Another option is extending DocumentPlugin to modify the WSDL/XSD object, the parsed() method (see the docs) will be called twice (one for the WSDL, another for the XSD).Jobye
Brilliant, thanks!! Interestingly enough I had to correct the envelope at the exact same place (structurally) as the initial example. Is there a pattern here?Earthly
As i see, ns naming order (ns{x} and uri mapping) is random and it changes for every Client instance. So i added this: array_ns = {v: k for k, v in context.envelope.nsprefixes.items()}['http://mylonguri'] And yes, it worked. And in most cases, second child of the body will be the method definition. So you can check context.envelope.getChild('Body').getChild('MyMethod') object if that request is really for your method which you want to fix or not, and you can reach other fields conditionally like that.Whitherward
B
2

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.

Blanding answered 22/3, 2013 at 8:35 Comment(0)
T
1

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)
Theophilus answered 13/6, 2012 at 7:58 Comment(0)
B
-1

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
Brasca answered 25/9, 2013 at 9:37 Comment(1)
Your method doens't work very well: WebFault: Server raised fault: 'The root element for the request could not be determined.Manic

© 2022 - 2024 — McMap. All rights reserved.