SOAP with Python objects
Dot access, attributes, lists and namespaces - SOAP messages as plain Python objects with no schemas or WSDL.
Every SOAP request you send and every response you receive is a SOAPMessage - a dynamic Python object where attributes are XML elements. You never declare its shape upfront, you never touch a schema, and no code is ever generated from a WSDL.
from zato.common.soap.message import SOAPMessage
request = SOAPMessage()
request.namespace = 'urn:cdc:iisb:2014'
request.hl7Message = 'MSH|^~\\&|...'
That is a complete request body. The rules below are the whole contract.
The rules
1. Dot access 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'
2. Assignment order is wire 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. Brackets are attributes
Brackets set and read XML attributes:
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 are 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 choose the XML form
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 assign | The wire receives |
|---|---|
str | The text as-is |
int, float, Decimal | The number's canonical form |
bool | true or false |
datetime | An xs:dateTime timestamp |
date | An xs:date value |
None | An xsi:nil element |
bytes | An MTOM part when the connection has MTOM enabled, inline base64 otherwise |
6. Reading mirrors writing
Responses come back as the same kind of object, so reading is dot access again - 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 include their protocol context
Two reserved attributes ride along on responses when the connection uses the matching blocks:
response.addressing- the reply's WS-Addressing headers:action,message_id,relates_toresponse.attachments- the reply's MTOM parts, each withcontent_id,content_typeanddatabytes
Faults are exceptions
A SOAP fault of either version surfaces as a single exception type, SOAPFault, raised before your code sees any part of the response:
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.