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

spynearrow-up-right 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:

Concept
Purpose

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

The @rpc decorator is the core of spyne's operation definition.

  • Positional arguments to @rpc are the input parameter types in order โ€” String, Integer

  • _returns is the return type

  • ctx is the spyne context (request metadata, headers, etc.) โ€” always first, not an input parameter

  • Parameter 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 @rpc method becomes an operation in the portType

  • Each ComplexModel class becomes an xsd:complexType

  • The tns argument to Application becomes the targetNamespace

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 ComplexModel

  • Operations with @rpc

  • XSD 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