Part 3: Building SOAP Services with Python and spyne
Why Build a SOAP Server?
Most of the time, developers interact with SOAP as a client โ consuming an existing enterprise service. But there are situations where you need to expose your own service using SOAP:
You are building an API that must integrate with an enterprise platform that only speaks SOAP (SAP, Oracle, legacy CRM)
You are standing up a mock service to test your client code independently
You are migrating an old Java or .NET SOAP service to Python and need to maintain the same contract
I built my first spyne service when I needed to provide a data feed to an insurance platform that required a specific WSDL contract. The platform could not consume REST. This section uses a similar inventory management service as the example โ the kind of service you would genuinely build for a B2B integration.
spyne Overview
spyne is a Python library for building SOAP (and other protocol) services. It handles:
WSDL generation based on your Python classes
XML serialisation and deserialisation
SOAP Fault generation
Multiple transports (HTTP via WSGI, TCP, etc.)
The core concepts in spyne:
ServiceBase
Base class for your service; define @rpc methods here
@rpc
Decorator that marks a method as a SOAP operation
ComplexModel
Base class for defining XSD complex types
Application
Wires the service, protocol, and transport together
WsgiApplication
WSGI adapter for hosting over HTTP
Project Structure
Step 1: Define Data Models
Define your XSD types as Python classes that extend ComplexModel.
ComplexModel classes map directly to XSD complexType definitions in the generated WSDL. When spyne generates the WSDL, it reads these class attributes and produces the corresponding XSD automatically.
Step 2: Define the Service
The service class inherits from ServiceBase and defines operations using the @rpc decorator.
Understanding @rpc
@rpcThe @rpc decorator is the core of spyne's operation definition.
Positional arguments to
@rpcare the input parameter types in order โString,Integer_returnsis the return typectxis the spyne context (request metadata, headers, etc.) โ always first, not an input parameterParameter names in the function signature become the XSD element names in the WSDL
Step 3: Wire the Application
Setting validator="lxml" on in_protocol enables XSD validation of incoming requests. Requests that do not match the schema return a SOAP Fault automatically without reaching your service code.
Step 4: Serve Over HTTP with Flask
Alternative: Serve with wsgiref Directly
For local development and testing, Python's built-in wsgiref is simpler:
Start the server:
Viewing the Generated WSDL
Once the server is running, spyne automatically exposes the WSDL at ?wsdl:
You can also load it in a browser or import it into SoapUI for visual inspection.
The WSDL generated by spyne maps directly to what you defined:
Each
@rpcmethod becomes anoperationin theportTypeEach
ComplexModelclass becomes anxsd:complexTypeThe
tnsargument toApplicationbecomes thetargetNamespace
Calling the Service
With the server running, here is a quick smoke test using zeep:
Logging Incoming SOAP Messages
During development, I always add logging to inspect raw requests. In spyne, you handle this with an event handler on the application.
Common spyne Pitfalls
Things that caught me when I first built a spyne service:
1. Parameter names in @rpc become XSD element names. If your method has product_id: str, the WSDL will have <product_id> in the input element. This matters when you need the WSDL to match an existing contract โ use the exact capitalization expected.
2. ctx is not a typed parameter. spyne automatically passes the context as the first argument. Do not include it in @rpc(...) type list.
3. Array(SomeComplexModel) generates the correct unbounded list type. Using a plain Python list will not be serialised correctly โ always use Array(T).
4. The _returns must be set explicitly. If missing, spyne generates a void return, and your response data will not appear in the WSDL or be serialised.
5. Use Decimal not float for monetary values. Float loses precision in XML serialisation.
What's Next
You have built a working SOAP service with spyne:
Data models with
ComplexModelOperations with
@rpcXSD validation on incoming requests
HTTP hosting with wsgiref and Flask
Logging with application event handlers
In Part 4, we switch to the client side and use zeep to consume SOAP services robustly โ handling complex types, managing sessions, caching WSDLs, and debugging transport issues.
Last updated