Zeep Usage: Do I have to build a nested dict or are there other options?

71 Views Asked by At

I have working XML for hitting a SOAP API. However, I've been asked to use zeep to build this in my python project.

I'm new to zeep and used to Java SOAP tools (albeit 10+ years ago).

Do I understand correctly that I need to build a nested python dict and pass that into the zeep client? No other options?

This is my working XML from my tests in Postman:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope 
               xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:bsvc="urn:com.workday/bsvc"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
               xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <soapenv:Header>
        <wsse:Security soapenv:mustUnderstand="1">
            <wsse:UsernameToken>
                <wsse:Username>ISU_Get_Worker_Photo@mycompany_preview</wsse:Username>
                <wsse:Password
                           Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">xxxxxxxxxxx</wsse:Password>
            </wsse:UsernameToken>
        </wsse:Security>
    </soapenv:Header>
    <soapenv:Body>
        <bsvc:Change_Work_Contact_Information_Request bsvc:version="v42.0">
            <bsvc:Business_Process_Parameters>
                <bsvc:Auto_Complete>true</bsvc:Auto_Complete>
                <bsvc:Run_Now>true</bsvc:Run_Now>
            </bsvc:Business_Process_Parameters>
            <bsvc:Change_Work_Contact_Information_Data>
                <bsvc:Event_Effective_Date>2023-10-25</bsvc:Event_Effective_Date>
                <bsvc:Person_Reference>
                    <bsvc:ID bsvc:type="Employee_ID">9476</bsvc:ID>
                </bsvc:Person_Reference>
                <bsvc:Person_Contact_Information_Data>
                    <bsvc:Person_Phone_Information_Data bsvc:Replace_All="false">
                        <bsvc:Phone_Information_Data bsvc:Delete="false">
                            <bsvc:Phone_Data>
                                <bsvc:Device_Type_Reference bsvc:Descriptor="?">
                                    <!--Zero or more repetitions:-->
                                    <bsvc:ID bsvc:type="Phone_Device_Type_ID">Landline</bsvc:ID>
                                </bsvc:Device_Type_Reference>
                                <!--Optional:-->
                                <bsvc:Country_Code_Reference bsvc:Descriptor="?">
                                    <!--Zero or more repetitions:-->
                                    <bsvc:ID bsvc:type="Country_Phone_Code_ID">USA_1</bsvc:ID>
                                </bsvc:Country_Code_Reference>
                                <bsvc:Complete_Phone_Number>8444877900</bsvc:Complete_Phone_Number>
                                <bsvc:Extension></bsvc:Extension>
                            </bsvc:Phone_Data>
                            <bsvc:Usage_Data bsvc:Public="true">
                                <!--1 or more repetitions:-->
                                <bsvc:Type_Data bsvc:Primary="true">
                                    <bsvc:Type_Reference bsvc:Descriptor="?">
                                        <!--Zero or more repetitions:-->
                                        <bsvc:ID bsvc:type="Communication_Usage_Type_ID">Work</bsvc:ID>
                                    </bsvc:Type_Reference>
                                </bsvc:Type_Data>
                            </bsvc:Usage_Data>
                        </bsvc:Phone_Information_Data>
                    </bsvc:Person_Phone_Information_Data>
                </bsvc:Person_Contact_Information_Data>
            </bsvc:Change_Work_Contact_Information_Data>
        </bsvc:Change_Work_Contact_Information_Request>
    </soapenv:Body>
</soapenv:Envelope>

My understanding from what I've read is that I would do something like this (using the "root" node at the top of the body...)

import zeep, os 
from zeep import Client 
from zeep.wsse.username import UsernameToken
    
hostname = 'host'
    tenant_name = 'mycompany'
    employee_id = '1234'
    user = os.getenv('user') 
    password = os.getenv('password') 
    url = f'https://{hostname}.workday.com/ccx/service/{tenant_name}/Human_Resources/v36.2?wsdl'
    
client = Client(url, wsse=UsernameToken(user, password)) 

request_dict = { 
    A Big Nested Dict Of All the Other XML Tags in my Request
} 


   
client.service.Change_Work_Contact_Information_Request(request_dict)

... And, that "request_dict" must contain the correct representation of all the other nested XML tags in my request in a "nested dict" format.

In other words - no objects I can call to set the various tag values, it HAS to be a dict.

I see examples like this:

wsdl = 'http://www.soapclient.com/xml/soapresponder.wsdl'
client = zeep.Client(wsdl=wsdl)
print(client.service.Method1('Zeep', 'is cool'))

But I assume that only works because 'Method1' is so simple and there's no option for anything like this in my case - given my much more complex XML.

Thanks.

1

There are 1 best solutions below

0
On

I'll answer my own question.

From the doc, here: https://readthedocs.org/projects/python-zeep/downloads/pdf/master/#:~:text=Zeep%20inspects%20the%20WSDL%20document,interface%20to%20a%20SOAP%20server.

Section 5.6.2 Using Factories

When you need to create multiple types the Client.get_type() calls to retrieve the type class and then instantiating them can be a bit verbose. To simplify this you can use a factory object.

from zeep import Client
client = Client('http://my-enterprise-endpoint.com')
factory = client.type_factory('ns0')
user = factory.User(id=1, name='John')

You will find, however that the names of things in your XML (if that's how you did things at first), may not match what zeep generates.

Here is an example from my code (currently under construction)

Phone_Device_TypeObjectIDType = factory.Phone_Device_TypeObjectIDType('Landline')
Phone_Device_TypeObjectType = factory.Phone_Device_TypeObjectType(Phone_Device_TypeObjectIDType)
Country_Phone_CodeObjectIDType = factory.Country_Phone_CodeObjectIDType('USA_1')
Country_Phone_CodeObjectType = factory.Country_Phone_CodeObjectType(Country_Phone_CodeObjectIDType)
Phone_Core_DataType = factory.Phone_Core_DataType(
        Phone_Device_TypeObjectType,
        Country_Phone_CodeObjectType,
        Complete_Phone_Number='8444877900',
        Extension='1111'
    )

And the relevant section from my XML in Postman:

<bsvc:Phone_Data>
                                <bsvc:Device_Type_Reference bsvc:Descriptor="?">
                                    <!--Zero or more repetitions:-->
                                    <bsvc:ID bsvc:type="Phone_Device_Type_ID">Landline</bsvc:ID>
                                </bsvc:Device_Type_Reference>
                                <!--Optional:-->
                                <bsvc:Country_Code_Reference bsvc:Descriptor="?">
                                    <!--Zero or more repetitions:-->
                                    <bsvc:ID bsvc:type="Country_Phone_Code_ID">USA_1</bsvc:ID>
                                </bsvc:Country_Code_Reference>
                                <bsvc:Complete_Phone_Number>8444877900</bsvc:Complete_Phone_Number>
                                <bsvc:Extension></bsvc:Extension>
                            </bsvc:Phone_Data>

You'll note the names are quite different.

For example, Country_Phone_Code_ID becomes Country_Phone_CodeObjectIDType

Per the doc, you'll want to run this first:

python -mzeep http://your_wsdl_address_here

And keep the output - I've had to search through that output for my XML tags and then (somewhat painstakingly) find the correct name in what zeep generated so that I can address it properly for the factory.

Also note that if the "name" in the output does NOT have the "ns0" associated with it, you have to set it directly as I did above for the Complete_Phone_Number and Extension fields.

Here is an example of what you'll find - the second field is the correct name to use when working with the factory.

Phone_Information_Data: ns0:Person_Phone_DataType

The first field (Phone_Information_Data) was what was in my XML, the second field (Person_Phone_DataType) is what I had to use to address the factory, like this...

Person_Phone_DataType = factory.Person_Phone_DataType(Phone_Core_DataType)

Hope this helps someone.