Skip to main content

A tolerant, self-documenting Python SOAP client library

Project description

soapix

A tolerant, self-documenting Python SOAP client library.

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.

Features

  • Tolerant validation — optional fields can be omitted; required None fields send xsi:nil instead 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 AsyncSoapClient with async/await
  • WSDL caching — in-memory and file-based caching with TTL
  • Retry & timeout — configurable per-client
  • Type stubs — full .pyi stubs for IDE autocomplete

Requirements

  • Python 3.10+
  • Dependencies: httpx, lxml, rich, pydantic

Installation

pip install soapix

Quick Start

from soapix import SoapClient

client = SoapClient("http://service.example.com/?wsdl")
result = client.service.GetUser(userId=123)
print(result["name"])

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 Number of retries on connection/timeout errors
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)

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="Alice", email="alice@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": "Alice"},   # nested element
#   "items": [                                  # repeated element → list
#     {"sku": "A1", "qty": 2},
#     {"sku": "B3", "qty": 1},
#   ]
# }

result = client.service.GetUser(userId=1)
# {"userId": 1, "name": "Alice", "email": "alice@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.


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 pickle for 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

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")

You can also use DocsGenerator directly if you have a parsed WSDL document:

from soapix.docs.generator import DocsGenerator
from soapix.wsdl.parser import WsdlParser

doc = WsdlParser().load("service.wsdl")
gen = DocsGenerator(doc)
gen.render(output="terminal")
gen.render(output="markdown", path="api_docs.md")
gen.render(output="html",     path="api_docs.html")

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

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 HttpError (connection failures) and TimeoutError. Server-side SOAP faults (SoapFaultError) are not retried.


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
Async support Partial No Native
WSDL caching No No Yes
Retry & timeout Manual Manual Built-in
Type stubs Partial No Yes
Python 3.10+ Yes No Yes

Notes:

  • Zeep meaningful errors: Zeep raises structured Fault exceptions but does not include sent payload, endpoint, or human-readable hints in the error output.
  • Zeep async (Partial): Zeep supports async via a separate AsyncTransport configuration; soapix's AsyncSoapClient works natively with async/await and 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 suds library is unmaintained. Comparison is based on suds-community, its last active fork.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

soapix-0.2.2.tar.gz (40.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

soapix-0.2.2-py3-none-any.whl (35.4 kB view details)

Uploaded Python 3

File details

Details for the file soapix-0.2.2.tar.gz.

File metadata

  • Download URL: soapix-0.2.2.tar.gz
  • Upload date:
  • Size: 40.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for soapix-0.2.2.tar.gz
Algorithm Hash digest
SHA256 fae1d6c8732fc66e94ff4593fc044285ea17ee50a119f98a4d99b70035926044
MD5 a9f8e60bc276b44da87395fa96646cfc
BLAKE2b-256 de6b5a89e3740f2dbcf538db7e0521d2da8f1d0849eaf8d8642d9c158062cb59

See more details on using hashes here.

File details

Details for the file soapix-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: soapix-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 35.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for soapix-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 60a37b489f4c3e3f9996e6fe9783067a51308a84140918bf992216b808dc7088
MD5 226294f8590a1542a0816ad92b73086e
BLAKE2b-256 955648e260a361aacf56daaaff3a726464f3febb6a77faa4286dcc7890b0912a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page