Skip to content

Instantly share code, notes, and snippets.

@jjesusfilho
Forked from FilBot3/python-zeep-example.md
Created August 20, 2023 23:02
Show Gist options
  • Save jjesusfilho/ad9328784bf9c68afbba452b1813974d to your computer and use it in GitHub Desktop.
Save jjesusfilho/ad9328784bf9c68afbba452b1813974d to your computer and use it in GitHub Desktop.
Python Zeep SOAP Example Write Up

Python Zeep SOAP Client

First, we'll look at the SOAP URL and see what Prefixes, Global Elements, Global Types, Bindings, and Services are provided by the SOAP Service.

You can do this by running zeep as a CLI tool.

export WSDL_URL="http://www.dneonline.com/calculator.asmx?WSDL"
python -m zeep $WSDL_URL

This should return and print a lot of data to the screen. We'll walk through it using Python after examining it. Here is what should appear:

Prefixes:
     xsd: http://www.w3.org/2001/XMLSchema
     ns0: http://tempuri.org/

Global elements:
     ns0:Add(intA: xsd:int, intB: xsd:int)
     ns0:AddResponse(AddResult: xsd:int)
     ns0:Divide(intA: xsd:int, intB: xsd:int)
     ns0:DivideResponse(DivideResult: xsd:int)
     ns0:Multiply(intA: xsd:int, intB: xsd:int)
     ns0:MultiplyResponse(MultiplyResult: xsd:int)
     ns0:Subtract(intA: xsd:int, intB: xsd:int)
     ns0:SubtractResponse(SubtractResult: xsd:int)
     

Global types:
     xsd:anyType
     xsd:ENTITIES
     xsd:ENTITY
     xsd:ID
     xsd:IDREF
     xsd:IDREFS
     xsd:NCName
     xsd:NMTOKEN
     xsd:NMTOKENS
     xsd:NOTATION
     xsd:Name
     xsd:QName
     xsd:anySimpleType
     xsd:anyURI
     xsd:base64Binary
     xsd:boolean
     xsd:byte
     xsd:date
     xsd:dateTime
     xsd:decimal
     xsd:double
     xsd:duration
     xsd:float
     xsd:gDay
     xsd:gMonth
     xsd:gMonthDay
     xsd:gYear
     xsd:gYearMonth
     xsd:hexBinary
     xsd:int
     xsd:integer
     xsd:language
     xsd:long
     xsd:negativeInteger
     xsd:nonNegativeInteger
     xsd:nonPositiveInteger
     xsd:normalizedString
     xsd:positiveInteger
     xsd:short
     xsd:string
     xsd:time
     xsd:token
     xsd:unsignedByte
     xsd:unsignedInt
     xsd:unsignedLong
     xsd:unsignedShort

Bindings:
     Soap11Binding: {http://tempuri.org/}CalculatorSoap
     Soap12Binding: {http://tempuri.org/}CalculatorSoap12

Service: Calculator
     Port: CalculatorSoap (Soap11Binding: {http://tempuri.org/}CalculatorSoap)
         Operations:
            Add(intA: xsd:int, intB: xsd:int) -> AddResult: xsd:int
            Divide(intA: xsd:int, intB: xsd:int) -> DivideResult: xsd:int
            Multiply(intA: xsd:int, intB: xsd:int) -> MultiplyResult: xsd:int
            Subtract(intA: xsd:int, intB: xsd:int) -> SubtractResult: xsd:int

     Port: CalculatorSoap12 (Soap12Binding: {http://tempuri.org/}CalculatorSoap12)
         Operations:
            Add(intA: xsd:int, intB: xsd:int) -> AddResult: xsd:int
            Divide(intA: xsd:int, intB: xsd:int) -> DivideResult: xsd:int
            Multiply(intA: xsd:int, intB: xsd:int) -> MultiplyResult: xsd:int
            Subtract(intA: xsd:int, intB: xsd:int) -> SubtractResult: xsd:int

Let's take a look at this and go through each part.

The top, first part, Prefixes.

Prefixes:
     xsd: http://www.w3.org/2001/XMLSchema
     ns0: http://tempuri.org/

These are Schemas and Name spaces that provide a common structure and if any custom elements are added, they're in a name space so they don't overwrite the default stuff.

The next part is Global Elements.

Global elements:
     ns0:Add(intA: xsd:int, intB: xsd:int)
     ns0:AddResponse(AddResult: xsd:int)
     ns0:Divide(intA: xsd:int, intB: xsd:int)
     ns0:DivideResponse(DivideResult: xsd:int)
     ns0:Multiply(intA: xsd:int, intB: xsd:int)
     ns0:MultiplyResponse(MultiplyResult: xsd:int)
     ns0:Subtract(intA: xsd:int, intB: xsd:int)
     ns0:SubtractResponse(SubtractResult: xsd:int)

These show you name space and Service functions available. If there was a second service or a version 2, it would most likely show the same service, just in a different namespace.

The Global Types section.

Global types:
     xsd:anyType
     xsd:ENTITIES
     xsd:ENTITY
     xsd:ID
     xsd:IDREF
     xsd:IDREFS
     xsd:NCName
     xsd:NMTOKEN
     xsd:NMTOKENS
     xsd:NOTATION
     xsd:Name
     xsd:QName
     xsd:anySimpleType
     xsd:anyURI
     xsd:base64Binary
     xsd:boolean
     xsd:byte
     xsd:date
     xsd:dateTime
     xsd:decimal
     xsd:double
     xsd:duration
     xsd:float
     xsd:gDay
     xsd:gMonth
     xsd:gMonthDay
     xsd:gYear
     xsd:gYearMonth
     xsd:hexBinary
     xsd:int
     xsd:integer
     xsd:language
     xsd:long
     xsd:negativeInteger
     xsd:nonNegativeInteger
     xsd:nonPositiveInteger
     xsd:normalizedString
     xsd:positiveInteger
     xsd:short
     xsd:string
     xsd:time
     xsd:token
     xsd:unsignedByte
     xsd:unsignedInt
     xsd:unsignedLong
     xsd:unsignedShort

This part shows all the different types that are supported by the WSDL and the remote SOAP service. Most of these will be supported by the SOAP Service and your programming language.

The Bindings.

Bindings:
     Soap11Binding: {http://tempuri.org/}CalculatorSoap
     Soap12Binding: {http://tempuri.org/}CalculatorSoap12

These Bindings allow you to specify different Ports, or versions of the Service we're wanting to use.

Now we get to the main fun part, interacting with the Service.

Service: Calculator
     Port: CalculatorSoap (Soap11Binding: {http://tempuri.org/}CalculatorSoap)
         Operations:
            Add(intA: xsd:int, intB: xsd:int) -> AddResult: xsd:int
            Divide(intA: xsd:int, intB: xsd:int) -> DivideResult: xsd:int
            Multiply(intA: xsd:int, intB: xsd:int) -> MultiplyResult: xsd:int
            Subtract(intA: xsd:int, intB: xsd:int) -> SubtractResult: xsd:int

     Port: CalculatorSoap12 (Soap12Binding: {http://tempuri.org/}CalculatorSoap12)
         Operations:
            Add(intA: xsd:int, intB: xsd:int) -> AddResult: xsd:int
            Divide(intA: xsd:int, intB: xsd:int) -> DivideResult: xsd:int
            Multiply(intA: xsd:int, intB: xsd:int) -> MultiplyResult: xsd:int
            Subtract(intA: xsd:int, intB: xsd:int) -> SubtractResult: xsd:int

Here we have a Service named Calculator with two Ports. For now we won't worry about which port we use, but we can bind to specific ones if we desire so. We'll simply call this by Calculator in our application.

The Python Code

Using the SOAP WSDL Endpoint we just used, we'll do a simple action, then walk through the objects.

#!/usr/bin/env python
"""Python Zeep Client Example
"""


import os
import zeep


def main():
    """Main
    """
    wsdl_url = os.environ.get('WSDL_URL')
    soap = zeep.Client(wsdl=wsdl_url, 
                       service_name="Calculator",
                       port_name="CalculatorSoap12")
    result = soap.service.Add(5, 5)
    
    assert result == 10
    

if __name__ == '__main__':
    main()

This script should return an integer with our two numbers added together. We've specified a few parameters to our zeep.Client function. First was our wsdl=. This is the URL to the WSDL document we're wanting to use. The service_name= is denoted by the Services listed in the output or in the WSDL we're using. The port_name= is also part of the Services listed in the WSDL or output from the previous command. We make our client, then we use soap.service.Add(5, 5). How does the program know to use that Add()? It's because we specified which Service, and Port to bind to which provides that function. You may notice there are two different Ports. One is a SOAP 11 and one is a SOAP 12 port as kind of denoted by the name of the Port.

How could one programmatically go through this? We'll use Python's dir() function. To my mind at first, this meant, directory. However, it's used to see what attributes are available to an object in Python. These objects are typically returned in an Array so you could iterate through that list and see what's available.

Let's walk through a few objects. Start up the Python REPL tool.

python

Now we can start our journey.

import os
import zeep
soap = zeep.Client(wsdl=os.environ.get('WSDL_URL'))
dir(soap)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_default_port_name', '_default_service', '_default_service_name', '_default_soapheaders', '_get_port', '_get_service', 'bind', 'create_message', 'create_service', 'get_element', 'get_type', 'namespaces', 'plugins', 'service', 'set_default_soapheaders', 'set_ns_prefix', 'settings', 'transport', 'type_factory', 'wsdl', 'wsse']

The parts we're really interested in are the ones that don't contain __ in them. So let's start with the wsdl attribute.

dir(soap.wsdl)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_add_definition', '_definitions', '_get_xml_document', 'bindings', 'dump', 'location', 'messages', 'port_types', 'services', 'settings', 'transport', 'types']

Here we can start to see how Python presents the WSDL we're using. Since no one, literally NO ONE, likes working with XML, it's presented as Python primitives. Let's checkout the soap.wsdl.services since we're working with Services above.

dir(soap.wsdl.services)
['__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'move_to_end', 'pop', 'popitem', 'setdefault', 'update', 'values']

Now, you can see what items are available in the Services. You can also view the keys as well.

soap.wsdl.services.keys()
odict_keys(['Calculator'])
soap.wsdl.services.items()
odict_items([('Calculator', <Service(name='Calculator', ports=OrderedDict([('CalculatorSoap', <Port(name='CalculatorSoap', binding=<Soap11Binding(name='{http://tempuri.org/}CalculatorSoap', port_type=<PortType(name='{http://tempuri.org/}CalculatorSoap')>)>, {'address': 'http://www.dneonline.com/calculator.asmx'})>), ('CalculatorSoap12', <Port(name='CalculatorSoap12', binding=<Soap12Binding(name='{http://tempuri.org/}CalculatorSoap12', port_type=<PortType(name='{http://tempuri.org/}CalculatorSoap')>)>, {'address': 'http://www.dneonline.com/calculator.asmx'})>)]))>)])

This shows us a few things, but isn't super helpful all the time. Let's walk through the soap.service object.

dir(soap.service)
['Add', 'Divide', 'Multiply', 'Subtract', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__self_class__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__thisclass__']

Whoa! Look at that, we've got our Methods we're wanting. There's not much else to explore at this point. We know we can call soap.service.Add() and pass it the parameters specified in the WSDL and the very first command output we made.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment