SOAP message objects

Build SOAP requests and read responses as Python objects - elements, attributes, lists and namespaces, with no schemas.

SOAPMessage represents the body of a SOAP request or response. Each attribute of the object corresponds to an XML element. The structure of a message is not declared in advance, and no schema or WSDL is required.

from zato.common.soap.message import SOAPMessage

request = SOAPMessage()
request.namespace = 'urn:cdc:iisb:2014'
request.hl7Message = 'MSH|^~\\&|...'

The preceding code constructs a complete request body. The following rules define how a SOAPMessage maps to XML.

Mapping rules

1. Attribute assignment creates elements

Assigning to an attribute creates an element. Intermediate elements spring into existence as you go deeper - there is nothing to declare first:

request = SOAPMessage()
request.order.customer.name = 'Jane Smith'
request.order.customer.city = 'Geneva'
<order>
    <customer>
        <name>Jane Smith</name>
        <city>Geneva</city>
    </customer>
</order>

2. Element order follows assignment order

Elements appear on the wire in exactly the order you assigned them. Nothing is reordered, which matters because many SOAP endpoints validate element order strictly - if the documentation of a system says username comes before password, assign them in that order and that is the order they will have.

3. Subscripts set XML attributes

Brackets set and read XML attributes:

request.slot['name'] = 'creationTime'
request.slot.value = '2026-01-15T10:30:00'
<slot name="creationTime">
    <value>2026-01-15T10:30:00</value>
</slot>

One bracket key is reserved: namespace. Setting message.namespace picks the default namespace for the whole message, while setting it on a node overrides the namespace from that node down:

request = SOAPMessage()
request.namespace = 'urn:example:orders'

# This one subtree uses a different namespace
request.metadata['namespace'] = 'urn:example:common'
request.metadata.created_by = 'api'

4. Lists produce repeated elements

Assigning a list produces one element per item - both for scalars and for nested SOAPMessage nodes:

line1 = SOAPMessage()
line1.product = 'AA-100'
line1.quantity = 3

line2 = SOAPMessage()
line2.product = 'BB-200'
line2.quantity = 1

request.order.lines.line = [line1, line2]
<order>
    <lines>
        <line><product>AA-100</product><quantity>3</quantity></line>
        <line><product>BB-200</product><quantity>1</quantity></line>
    </lines>
</order>

5. Python types determine the XML representation

The type of the value dictates its lexical form on the wire - you assign natural Python values and the platform writes what the receiving system expects:

You assignThe wire receives
strThe text as-is
int, float, DecimalThe number's canonical form
booltrue or false
datetimeAn xs:dateTime timestamp
dateAn xs:date value
NoneAn xsi:nil element
bytesAn MTOM part when the connection has MTOM enabled, inline base64 otherwise

6. Responses are read through attributes

Responses are SOAPMessage objects as well, so attributes read as they were assigned - repeated elements are lists, xsi:nil reads as None, and you never need to know or spell out the namespaces of the reply:

response = conn.invoke('getOrder', request)

order = response.getOrderResponse.order
status = order.status

for line in order.lines.line:
    self.logger.info('%s x %s', line.product, line.quantity)

An element that has attributes is always a node - its attributes read through brackets and its text through str():

# <status code="42">Accepted</status>
code = response.status['code']   # '42'
text = str(response.status)      # 'Accepted'

7. Responses expose protocol metadata

Two reserved attributes are added to responses when the connection uses the matching blocks:

  • response.addressing - the reply's WS-Addressing headers: action, message_id, relates_to
  • response.attachments - the reply's MTOM parts, each with content_id, content_type and data bytes

Fault handling

A SOAP fault of either version is reported as a single exception type, SOAPFault, raised before any part of the response reaches your code:

from zato.common.soap.common import SOAPFault

try:
    response = conn.invoke('submitSingleMessage', request)
except SOAPFault as fault:
    self.logger.warning('Rejected: %s %s', fault.code, fault.reason)

fault.code and fault.reason are strings and fault.detail is a dot-accessed SOAPMessage holding whatever the endpoint put in the fault's detail section.

Learn more