Search code examples
pythondictionarysoapxmltodict

How to convert dict to suds complex suds type


I am trying to send a request to a soap server, using suds which should look like this:

<SOAP-ENV:Envelope xmlns:ns0="http://example.com/wsdl/abc/model/v1" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://example.com/xsd/abc/common/v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<ns1:Body>
   <ns0:myMethod>
      <unique_id>5211c04b-9cf5-4368-a393-ed96d7b5489d</unique_id>
      <metadata>
         <ns2:StringMetadata id="user">test_user</ns2:StringMetadata>
         <ns2:StringMetadata id="filename">myfile.zip</ns2:StringMetadata>
         <ns2:StringMetadata id="unique_id">5211c04b-9cf5-4368-a393-ed96d7b5489d</ns2:StringMetadata>
      </metadata>
   </ns0:myMethod>
</ns1:Body>

Is there any way to do it by using only dict ?

I am trying this:

params = {
'unique_id' : uid,
"metadata": {
        'StringMetadata' : ['test_user', 'myfile.zip']
    }
 }

 caller.myMethod(**params)

which generating this request:

    <SOAP-ENV:Envelope xmlns:ns0="http://example.com/wsdl/abc/model/v1" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://example.com/xsd/abc/common/v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <ns1:Body>
      <ns0:myMethod>
         <unique_id>2ae5a60e-7928-4d4f-9e53-bafe2a003b8a</unique_id>
         <metadata>
            <ns2:StringMetadata>test_user</ns2:StringMetadata>
            <ns2:StringMetadata>myfile.zip</ns2:StringMetadata>
         </metadata>
      </ns0:myMethod>
   </ns1:Body>
</SOAP-ENV:Envelope>

So we can se that the id attributes are missing from my StringMetada. I am wanting to do it using only dict, because I am writing a kind of Rest to Soap proxy, which is getting a non-validated Json (or dict) from another caller. So it should be as dynamic as possible.

I tried to build on the fly elements (with factory.create) but it seems to be a headache... I would appreciate a solution by passing a special keyword for element attributes ('_id', '@id' or '#id')...

I also tried to declare my StringMetadata like this:

"metadata": {
        'StringMetadata' : [{'_id': 'user'}, 'myfile.zip']
    }

but it gave me:

    <ns2:StringMetadata id="user"/>
    <ns2:StringMetadata>myfile.zip</ns2:StringMetadata>

If you have a solution, by applying a monkey-patch (on the fly) to suds classes, please feel free also.

Regards


Solution

  • I finally managed to get it working after hours of suds understanding and debugging. I wrote a solution based on a suds monkey-patch. Thus my dict should be:

    params = {
    'unique_id' : uid,
    "metadata": {
            'StringMetadata' : [{'_id': 'user', '_text_':'test_user'},{'_id': 'filename', '_text_': 'myfile.zip'}]
        }
     }
    
     caller.myMethod(**params)
    

    '_text_' is a magic that will be detected by the patch and used as a node value. The '_id' will be used by suds as an attribute, every element starting by _ is interpreted as a node attribute, so my patch doesn't handle attributes.

    This is the patch:

    import functools
    from suds.mx.appender import *
    legacy_append = suds.mx.appender.ObjectAppender.append
    @functools.wraps(legacy_append)
    def _patch_object_append(self, parent, content):
        object = content.value
        if self.optional(content) and footprint(object) == 0:
            return
        child = self.node(content)
        for item in object:
             cont = Content(tag=item[0], value=item[1])
             if item[0] == '_text_':
                child.setText(item[1])
                continue
    
             Appender.append(self, child, cont)
        parent.append(child)
    
    suds.mx.appender.ObjectAppender.append= _patch_object_append
    

    This block can be either inserted before your suds instantiating or stored in a separated a script file and imported in your script. Personally, I used the second solution.

    Please notice also, that the keyword text was my choice, you can use '_value_' instead. It should be updated in this line:

         if item[0] == '_text_':
    

    Please feel free to use this patch. I think that this feature should be built-in in the Suds library

    Regards,