Zum Inhalt springen

Beyond the WSDL: Using Zeep with External XSD Schemas

4. August 2025 durch
Beyond the WSDL: Using Zeep with External XSD Schemas
Benjamin Wieser

Sometimes you have to use SOAP. You are not proud of it, but sometimes it happens. But when you do, you'd think a SOAP web service's WSDL would, you know, define the API. It's supposed to be a complete description, laying out the operations, the messages, and all the data types you're meant to use. If my SOAP API is designed like this, I can just ask my API client, like Zeep, to generate requests for me. I give it structured data, it calls the API for me. I don’t have to care about details.


But sometimes you run into WSDLs that are just… broken. The people who wrote it thought it would be a good idea create a generic, “reusable“ API. Instead of properly describing the API in definition, they rely on XSD:ANY, and put the description of these objects somewhere else, like an external XSD file. So now you're stuck, forced to manually connect the dots just to send a basic message.


Fortunately, Zeep is flexible enough to handle this kind of lazy architecture. You can manually bypass the WSDL's useless schema definition and load the external XSD file yourself. Let's look at how to deal with this irritating situation using a practical example.


The Problem: A Missing Schema


You're trying to interact with a service and the WSDL's message definition looks like this:


<xsd:element name="submitMessage">
   <xsd:complexType>
    <xsd:complexType>
        <xsd:sequence>
            <xsd:any minOccurs="0"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:element>

 

Seriously? An xsd:any? It's literally just a wildcard that tells the service, "Figure it out." So now, to actually get a message to go through, you have to dig up the separate open_MessageFormatC.xsd file and feed it into Zeep. Great.



The Solution: Loading the XSD Directly


The trick is to use Zeep's xsd.Schema class to parse your external schema file directly. This allows the library to get a complete, valid type definition from a source that actually has one.


import zeep
from zeep.xsd import xsd
from zeep.transports import Transport
from zeep.loader import load_external

# 1. Open the XSD file and parse it with lxml.etree
with open('open_MessageFormatC.xsd', 'rb') as f:
    xmlschema_doc = load_external(f, transport)

# 2. Create a Zeep schema object from the parsed document
transport = Transport()
schema = xsd.Schema(xmlschema_doc, transport=transport)

# 3. Get the root element type from the schema
MessageEnvelope = schema.get_element('ns0:MessageEnvelope')

# 4. Construct the any object that you can pass into the zeep service
message = zeep.xsd.AnyObject(MessageEnvelope, MessageEnvelope(**{
    'ListedData': [
        {
            'Organization': {
                'DocumentScopeAssignmentID': 'handover',
                'ID': {
                    'collectionID': '9008390104026',
                    '_value_1': '12345'
                }
            }
        }
    ],
    'MessageData': {
        'Shipment': {
            'UUID': 'SHIPMENT_UUID',
            'ShipmentItem': {
                'WasteTypeID': {
                    'collectionID': '5174',
                    '_value_1': 'WASTETYPE_GTIN'
                },
            }
        }
    }
}))


# 5. Create the SOAP Client and send SOAP request to the server
client = Client(wsdl=WSDL_URL)
client.service.ShareDocument('_value_1'= message)


Step-by-Step Breakdown:


  1. Parsing the XSD: We start by opening the XSD file and using zeeps load_external method.
  2. Creating the Zeep Schema: The xsd.Schema class is the hero here. It takes our parsed XSD document and a transport object. This transport object is important because the XSD might contain references to other schemas, and Zeep will use the transport to fetch them.
  3. Getting the Root Element: Now that Zeep understands our schema, we can ask for the specific element we need. schema.get_element('ns0:MessageEnvelope') retrieves the MessageEnvelope element and its entire complex structure, including all nested elements and types. The 'ns0:' prefix is the XML namespace, which is crucial for locating the correct element.
  4. Constructing the Message: We use zeep.xsd.AnyObject to wrap our data. The first argument is the type we just retrieved (MessageEnvelope). The second argument is a dictionary that mirrors the structure of the XML message we want to create. Zeep will use the schema definition to validate this dictionary and convert it into the correct XML format.



A note on API design.


Idealy you could skip all this nonsense, call the creator of the API and tell them, that a SOAP API is supposed to be descriptive. Unfortionately that is rarely possible. If you are ever in the situation to build a API, please don’t fall into the trap of making it “generic“ and “reusable“. All you are doing is make it harder for the person calling your API and going against your and your users tooling. All for the questionable benefit of saving time in the future. A benefit that rarely appears. If you want to read more about my opionions on “reusable“ architecture, have a look at The myth of the generic UI.


Stichwörter