A tolerant, self-documenting Python SOAP client library
Project description
soapix
A tolerant, self-documenting Python SOAP client library with an interactive browser playground.
soapix is designed to work with real-world SOAP services that don't perfectly follow the spec — handling namespace quirks, loose validation, and unclear error messages that break other libraries.
Built for the real world: where WSDL files are messy, error messages are cryptic, and you need async out of the box.
Quick Start
pip install soapix
from soapix import SoapClient
client = SoapClient("http://service.example.com/?wsdl")
# Explore and test operations in the browser — no extra tools needed
client.serve()
# Or call directly from code
result = client.service.GetUser(userId=123)
print(result["name"])
client.serve() opens an interactive UI at http://localhost:8765 — test any operation instantly, directly from your WSDL, with no Postman, SoapUI, or extra setup required.
Features
- Interactive playground —
client.serve()launches a browser UI to test any operation instantly — no Postman, no SoapUI, no extra setup - Code generation —
client.generate()produces a fully typed Python client class with@dataclasstypes and IDE autocomplete - Tolerant validation — optional fields can be omitted; required
Nonefields sendxsi:nilinstead of crashing - Namespace tolerance — trailing slashes, case differences, and URI fragments are normalized automatically
- Auto-documentation — generates terminal, Markdown, and HTML docs directly from the WSDL
- Meaningful errors — exceptions include service name, method, endpoint, sent payload, and a human-readable hint
- Async support — native
AsyncSoapClientwithasync/await - WSDL caching — in-memory and file-based caching with TTL
- Retry & timeout — configurable per-client
- Type stubs — full
.pyistubs for IDE autocomplete
Requirements
- Python 3.9+
- Dependencies:
httpx,lxml,rich
Installation
pip install soapix
Calling Operations
Operations are accessed through client.service.<OperationName>(...) using keyword arguments that match the WSDL parameter names.
# Simple call
result = client.service.GetUser(userId=42)
# Multiple parameters
result = client.service.CreateUser(name="John Doe", email="john@example.com")
# Optional parameters can be omitted in tolerant mode (default)
result = client.service.GetUser(userId=42) # locale is optional — omitted
result = client.service.GetUser(userId=42, locale="en-US") # or explicitly passed
# Required field sent as None → xsi:nil in tolerant mode
result = client.service.GetUser(userId=None)
The return value is a plain Python dict (or scalar for leaf values). Nested elements become nested dicts; repeated elements become lists.
# Nested elements → nested dicts
result = client.service.GetOrder(orderId=1)
# {
# "orderId": 1,
# "customer": {"id": 42, "name": "John Doe"}, # nested element
# "items": [ # repeated element → list
# {"sku": "A1", "qty": 2},
# {"sku": "B3", "qty": 1},
# ]
# }
result = client.service.GetUser(userId=1)
# {"userId": 1, "name": "John Doe", "email": "john@example.com", "active": True}
Async Client
import asyncio
from soapix import AsyncSoapClient
async def main():
async with AsyncSoapClient("http://service.example.com/?wsdl") as client:
result = await client.service.GetUser(userId=123)
print(result["name"])
asyncio.run(main())
AsyncSoapClient accepts the same options as SoapClient. Use it as an async context manager to ensure the underlying HTTP connection is properly closed.
Interactive Playground
Local development only. The playground is intended for use on your local machine during development. It starts an unauthenticated HTTP server — do not run it on a remote server or expose it to a network.
Point soapix at a WSDL and instantly test any operation from your browser — no Postman, no SoapUI, no configuration.
client = SoapClient("https://service.example.com/?wsdl")
client.serve()
This starts a local HTTP server (default: http://localhost:8765) and opens your browser automatically. From the UI you can:
- Browse all operations in the sidebar
- Fill in input parameters with a form
- Execute calls and see the response as formatted JSON
- Filter operations by name with the search box
- Use Cmd+Enter (Mac) / Ctrl+Enter to execute quickly
soapix playground — UserService
Listening at http://localhost:8765
5 operation(s) available
Press Ctrl+C to stop
Options:
client.serve(
host="localhost", # interface to bind (default: 'localhost')
port=8765, # TCP port (default: 8765)
open_browser=True, # open browser automatically (default: True)
)
serve() is also available on AsyncSoapClient and can be called after entering the async context manager.
Code Generation
Generate a fully typed Python client class directly from a WSDL — with real method signatures, @dataclass types for nested parameters, and IDE autocomplete.
CLI
soapix generate http://service.example.com/?wsdl -o my_client.py
Or with a local file:
soapix generate service.wsdl -o my_client.py
Programmatic
client = SoapClient("http://service.example.com/?wsdl")
# Write to file
client.generate(path="my_client.py")
# Or get as string
code = client.generate()
print(code)
Example output
# Generated by soapix — do not edit manually
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
from soapix import SoapClient
@dataclass
class Credentials:
appKey: str
appSecret: Optional[str] = None
class UserServiceClient:
def __init__(self, wsdl: str = "http://service.example.com/?wsdl", **kwargs) -> None:
self._client = SoapClient(wsdl, **kwargs)
def GetUser(self, userId: int, locale: Optional[str] = None) -> dict:
"""Retrieves user information by ID."""
return self._client.service.GetUser(userId=userId, locale=locale)
def Login(self, credentials: Credentials) -> dict:
_kw: dict = {}
_kw["credentials"] = {"appKey": credentials.appKey, "appSecret": credentials.appSecret}
return self._client.service.Login(**_kw)
The generated class wraps SoapClient — all constructor options (timeout, retries, auth, verify, etc.) are forwarded via **kwargs.
Note: Re-run
generatewhenever the WSDL changes to keep the class in sync.
Auto-Documentation
soapix can generate human-readable documentation from the WSDL — terminal output, Markdown, or HTML.
Terminal
client.docs()
┌─────────────────────────────────────────────────────────┐
│ UserService │
├──────────────┬───────────────┬──────────┬──────────────┤
│ Operation │ Parameter │ Type │ Required │
├──────────────┼───────────────┼──────────┼──────────────┤
│ GetUser │ userId │ int │ Yes │
│ │ locale │ string │ No │
├──────────────┼───────────────┼──────────┼──────────────┤
│ CreateUser │ name │ string │ Yes │
│ │ email │ string │ Yes │
└──────────────┴───────────────┴──────────┴──────────────┘
Markdown
# Returns a string
md = client.docs(output="markdown")
# Writes to a file
client.docs(output="markdown", path="api_docs.md")
HTML
# Returns a string
html = client.docs(output="html")
# Writes to a file (includes a search box)
client.docs(output="html", path="api_docs.html")
Service Check
Before making your first call to an unfamiliar service, run check() to diagnose potential WSDL parsing issues without sending any requests.
client = SoapClient("https://service.example.com/?wsdl")
client.check()
Example output:
soapix check — UserService
Endpoint: https://service.example.com/
SOAP 1.1 | qualified NS: 1
✓ 5 operation(s) found
✓ Endpoint: https://service.example.com/
Operation Input fields Output fields Status
CreateUser 2 1 ✓
GetUser 2 4 ✓
...
All checks passed.
If something is wrong:
✗ No operations found ← WSDL may not have parsed correctly
⚠ Endpoint is empty ← wsdl:service element may be missing
✗ input unresolved ← fields declared but type chain could not be resolved
check() is also available on AsyncSoapClient and can be called before entering the async context manager.
Error Handling
soapix raises structured exceptions with actionable context.
from soapix.exceptions import (
SoapFaultError, # Server returned a soap:Fault
HttpError, # HTTP 4xx/5xx or connection failure
TimeoutError, # Request exceeded the timeout
SerializationError, # Python value could not be serialised to XML
WsdlParseError, # WSDL could not be read or parsed
WsdlNotFoundError, # WSDL URL or path not reachable
WsdlImportError, # xs:import could not be resolved
)
Exception hierarchy
SoapixError
├── WsdlParseError
│ ├── WsdlNotFoundError
│ └── WsdlImportError
├── SoapCallError
│ ├── SoapFaultError
│ ├── HttpError
│ └── TimeoutError
└── SerializationError
Catching errors
from soapix.exceptions import SoapFaultError, HttpError, TimeoutError
try:
result = client.service.GetUser(userId=999)
except SoapFaultError as e:
print(e.fault_code) # e.g. "Server"
print(e.fault_string) # e.g. "User not found"
print(e.detail) # raw XML detail block, if any
except TimeoutError:
print("Request timed out — increase timeout or check the endpoint")
except HttpError as e:
print(f"HTTP error: {e}")
Error messages include structured context:
'GetUser' call failed
Service : UserService
Method : GetUser
Endpoint : http://service.example.com/
Sent : {'userId': None}
Hint : userId is required (int) — None cannot be sent in strict mode
Configuration
All options are keyword-only and passed to the constructor.
client = SoapClient(
"http://service.example.com/?wsdl",
timeout=60.0, # HTTP timeout in seconds (default: 30.0)
retries=3, # Retry count on transient failures (default: 0)
strict=False, # Strict WSDL validation (default: False)
debug=True, # Print request/response XML to terminal (default: False)
cache=None, # Cache instance, or None to disable (default: MemoryCache)
)
| Option | Type | Default | Description |
|---|---|---|---|
timeout |
float |
30.0 |
HTTP request timeout in seconds |
retries |
int |
0 |
Retry attempts on 5xx server errors and connection/timeout failures (4xx errors are never retried) |
strict |
bool |
False |
If True, raises on missing required fields instead of sending xsi:nil |
debug |
bool |
False |
Prints colourised request and response XML to the terminal |
cache |
Cache | None |
MemoryCache |
WSDL parse cache; None disables caching |
verify |
bool | str |
True |
SSL verification: True (system certs), False (skip), or path to a CA bundle file |
auth |
tuple | None |
None |
HTTP Basic Auth credentials as (username, password) |
Strict Mode
By default, soapix operates in tolerant mode: missing required fields send xsi:nil, and unknown namespaces are silently normalised. Enable strict mode to raise exceptions instead:
client = SoapClient("http://service.example.com/?wsdl", strict=True)
# Raises SerializationError if a required field is missing
client.service.GetUser() # userId is required → raises
client.service.GetUser(userId=None) # None on required field → raises
SSL & Authentication
SSL verification
# Default — uses system certificate store
client = SoapClient("https://service.example.com/?wsdl")
# Custom CA bundle (corporate / self-signed certificates)
client = SoapClient("https://service.example.com/?wsdl", verify="/path/to/ca-bundle.pem")
# Disable SSL verification — development only, not recommended for production
client = SoapClient("https://service.example.com/?wsdl", verify=False)
To obtain the server's CA certificate:
openssl s_client -connect service.example.com:443 -showcerts 2>/dev/null \
| sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > ca.pem
HTTP Basic Auth
Some services require credentials to access the WSDL itself and/or to call operations. Pass auth as a (username, password) tuple — it applies to both WSDL fetching and all SOAP calls:
client = SoapClient(
"https://service.example.com/?wsdl",
auth=("username", "password"),
)
Combined
client = SoapClient(
"https://service.example.com/?wsdl",
verify="/path/to/ca-bundle.pem",
auth=("username", "password"),
)
Debug Mode
Enable debug=True to print the full SOAP envelope sent and the raw XML response to the terminal, with syntax highlighting via rich.
client = SoapClient("http://service.example.com/?wsdl", debug=True)
client.service.GetUser(userId=1)
# ── REQUEST ──────────────────────────────
# POST http://service.example.com/
# SOAPAction: "..."
#
# <?xml version="1.0" ...>
# <soap:Envelope ...>
# ...
# </soap:Envelope>
#
# ── RESPONSE (200 OK, 42ms) ──────────────
# <?xml version="1.0" ...>
# ...
WSDL Caching
soapix caches parsed WSDL documents to avoid re-fetching and re-parsing on every instantiation.
MemoryCache (default)
from soapix.cache import MemoryCache
cache = MemoryCache(
ttl=300, # Seconds until entries expire (default: 300, None = no expiry)
maxsize=64, # Max entries before oldest is evicted (default: 64)
)
client = SoapClient("http://service.example.com/?wsdl", cache=cache)
A module-level default cache is shared across all SoapClient instances that don't specify one. Use get_default_cache() to access it:
from soapix.cache import get_default_cache
get_default_cache().clear() # flush all cached WSDLs
FileCache
FileCache persists parsed WSDL documents to disk using pickle. Useful across process restarts.
from soapix.cache import FileCache
cache = FileCache(
cache_dir=".soapix_cache", # Directory to store cache files (created if absent)
ttl=3600, # Seconds until entries expire (default: 3600)
)
client = SoapClient("http://service.example.com/?wsdl", cache=cache)
Note: FileCache uses
picklefor serialisation. Only use it with WSDL sources you trust — do not load cache files from untrusted or user-supplied paths.
Disable caching
client = SoapClient("http://service.example.com/?wsdl", cache=None)
# WSDL is fetched and parsed on every instantiation
Retry & Timeout
client = SoapClient(
"http://service.example.com/?wsdl",
timeout=10.0, # fail fast
retries=3, # retry up to 3 times on connection/timeout errors
)
Retries apply to transient 5xx server errors (no SOAP body), connection failures (HttpError), and timeouts (TimeoutError). 4xx client errors and SSL failures are never retried.
If a 5xx response contains a soap:Fault body (common for authentication failures, invalid input, etc.), it is treated as a definitive fault — SoapFaultError is raised immediately without retrying.
Comparison
| Feature | Zeep | Suds | soapix |
|---|---|---|---|
| Tolerant validation | No | No | Yes |
| Namespace tolerance | Partial | Partial | Full |
| Meaningful errors | No | No | Yes |
| Auto documentation | No | No | Yes |
Interactive playground (serve()) |
No | No | Yes |
Code generation (generate()) |
Partial | No | Yes |
Service diagnostics (check()) |
No | No | Yes |
| Async support | Partial | No | Native |
| WSDL caching | No | No | Yes |
| Retry & timeout | Manual | Manual | Built-in |
| Type stubs | Partial | No | Yes |
| Python 3.9+ | Yes | No | Yes |
Notes:
- Zeep meaningful errors: Zeep raises structured
Faultexceptions but does not include sent payload, endpoint, or human-readable hints in the error output.- Zeep async (Partial): Zeep supports async via a separate
AsyncTransportconfiguration; soapix'sAsyncSoapClientworks natively withasync/awaitand no extra setup.- Zeep WSDL caching (No): Zeep re-parses the WSDL on every instantiation by default; caching requires a custom transport wrapper.
- Suds: The original
sudslibrary is unmaintained. Comparison is based onsuds-community, its last active fork.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file soapix-0.5.0.tar.gz.
File metadata
- Download URL: soapix-0.5.0.tar.gz
- Upload date:
- Size: 64.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c27f9012de804fa15b7575f604cba1dec78eaaaae0ac76bec4db91d703fb6488
|
|
| MD5 |
873c45fe5f3f91ce1f8ed15f013439f4
|
|
| BLAKE2b-256 |
d065269b6c0764c893ffeeb9935f5b24e4f973c924870a2c35c5766ad53f3015
|
File details
Details for the file soapix-0.5.0-py3-none-any.whl.
File metadata
- Download URL: soapix-0.5.0-py3-none-any.whl
- Upload date:
- Size: 54.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d1dbe4107abd510b7a7ca5b3092246b8c941ded1fc0ce343196795cfce7d123
|
|
| MD5 |
8372e4a9dd84406efd4eb816b2d35122
|
|
| BLAKE2b-256 |
91eab06563c591f44b04a1c1c0fc9a2a5db7dc15f7354770b774804ddd61c100
|