{"id":50470036,"url":"https://github.com/hitoshyamamoto/soapbar","last_synced_at":"2026-06-01T10:00:43.686Z","repository":{"id":350822799,"uuid":"1172554682","full_name":"hitoshyamamoto/soapbar","owner":"hitoshyamamoto","description":"Modern SOAP library for Python. Build servers with a SoapService class and soap_operation decorators (auto-generated WSDL), or drive a typed client from an existing WSDL. SOAP 1.1/1.2, all binding styles, WS-Security, MTOM, WS-Addressing. ASGI/WSGI-compatible. Hardened lxml parser and tests conformance suite.","archived":false,"fork":false,"pushed_at":"2026-06-01T05:25:22.000Z","size":972,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-01T06:17:56.378Z","etag":null,"topics":["api","json","python","rpc","soap","soap-client","soap-server","wsdl"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/soapbar/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hitoshyamamoto.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-04T12:49:43.000Z","updated_at":"2026-06-01T05:25:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hitoshyamamoto/soapbar","commit_stats":null,"previous_names":["hitoshyamamoto/soapbar"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/hitoshyamamoto/soapbar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hitoshyamamoto%2Fsoapbar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hitoshyamamoto%2Fsoapbar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hitoshyamamoto%2Fsoapbar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hitoshyamamoto%2Fsoapbar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hitoshyamamoto","download_url":"https://codeload.github.com/hitoshyamamoto/soapbar/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hitoshyamamoto%2Fsoapbar/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33769492,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","json","python","rpc","soap","soap-client","soap-server","wsdl"],"created_at":"2026-06-01T10:00:24.091Z","updated_at":"2026-06-01T10:00:43.680Z","avatar_url":"https://github.com/hitoshyamamoto.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# soapbar\n\n[![CI](https://github.com/hitoshyamamoto/soapbar/actions/workflows/push.yml/badge.svg?branch=main)](https://github.com/hitoshyamamoto/soapbar/actions/workflows/push.yml)\n[![PyPI](https://img.shields.io/pypi/v/soapbar.svg?logo=pypi\u0026logoColor=white)](https://pypi.org/project/soapbar/)\n[![Python versions](https://img.shields.io/pypi/pyversions/soapbar.svg?logo=python\u0026logoColor=white)](https://pypi.org/project/soapbar/)\n[![License](https://img.shields.io/pypi/l/soapbar.svg)](https://github.com/hitoshyamamoto/soapbar/blob/main/LICENSE)\n![Conformance](https://img.shields.io/badge/SOAP%20conformance-100%25-brightgreen)\n\nA SOAP library for Python — client, server, and WSDL handling.\n\nsoapbar implements SOAP 1.1 and 1.2 with all five binding styles, auto-generates WSDL from Python service classes, parses existing WSDL to drive a typed client, and integrates with any ASGI or WSGI framework via thin adapter classes. The XML parser is hardened against XXE attacks using lxml with `resolve_entities=False`.\n\n\u003e **Conformance** — soapbar ships with an internal conformance suite of **135 tests across 10 spec-mapped classes** (`tests/audit/test_compliance.py`) covering SOAP 1.1/1.2, WSDL 1.1, and WS-I Basic Profile 1.1. The suite encodes 46 checkpoints derived from F01–F09 original findings, G01–G11 gap findings, I01–I04 informational observations, and S10 (WS-I BSP X.509 token profile); all 46 pass. This is a self-administered test suite, not an independent third-party audit.\n\n---\n\n## Table of Contents\n\n1. [Features](#features)\n2. [Installation](#installation)\n3. [Quick start — server](#quick-start--server)\n4. [Binding styles and SOAP encoding](#binding-styles-and-soap-encoding)\n5. [Defining a service](#defining-a-service)\n6. [SOAP versions](#soap-versions)\n7. [Framework compatibility](#framework-compatibility)\n8. [WSDL](#wsdl)\n9. [Client](#client)\n10. [XSD type system](#xsd-type-system)\n11. [Fault handling](#fault-handling)\n12. [Security](#security)\n13. [WS-Security — UsernameToken](#ws-security--usernametoken)\n14. [MTOM/XOP](#mtomxop)\n15. [XML Signature and Encryption](#xml-signature-and-encryption)\n16. [WSDL schema validation](#wsdl-schema-validation)\n17. [One-way operations](#one-way-operations)\n18. [SOAP array attributes](#soap-array-attributes)\n19. [rpc:result (SOAP 1.2)](#rpcresult-soap-12)\n20. [Interoperability](#interoperability)\n21. [Real-world services](#real-world-services)\n22. [Architecture](#architecture)\n23. [Public API](#public-api)\n24. [Comparison with alternatives](#comparison-with-alternatives)\n25. [Development setup](#development-setup)\n26. [Inspired by](#inspired-by)\n27. [Learn more](#learn-more)\n28. [Known Limitations](#known-limitations)\n29. [License](#license)\n\n---\n\n## Features\n\n- SOAP 1.1 and 1.2 (auto-detected from envelope namespace; fault codes auto-translated)\n- All 5 WSDL/SOAP binding style combinations (RPC/Encoded, RPC/Literal, Document/Literal, Document/Literal/Wrapped, Document/Encoded)\n- Auto-generates WSDL from service class definitions — no config files needed\n- Parses existing WSDL to drive a typed client\n- ASGI adapter (`AsgiSoapApp`) and WSGI adapter (`WsgiSoapApp`)\n- XXE-safe hardened XML parser (lxml, `resolve_entities=False`, `no_network=True`, `load_dtd=False`)\n- Message size limit (10 MB default) and XML nesting depth limit (100 levels) — DoS protection\n- **WS-Security UsernameToken** — PasswordText and PasswordDigest (SHA-1) on both client and server\n- **XML Signature** — enveloped XML-DSIG signing and verification (`sign_envelope` / `verify_envelope`, requires `signxml`)\n- **XML Encryption** — AES-256-CBC body encryption with RSA-OAEP session-key wrapping (`encrypt_body` / `decrypt_body`, requires `cryptography`)\n- **MTOM/XOP** — send and receive SOAP messages with binary attachments; `SoapClient(use_mtom=True)` + `add_attachment()`; server decodes inbound MTOM automatically\n- **WSDL schema validation** — opt-in Body validation against WSDL-embedded XSD types (`SoapApplication(validate_body_schema=True)`)\n- **One-way MEP** — `@soap_operation(one_way=True)` returns HTTP 202 with empty body\n- **SOAP array attributes** — `enc:itemType`/`enc:arraySize` (SOAP 1.2) and `SOAP-ENC:arrayType` (SOAP 1.1) emitted automatically\n- **Multi-reference encoding** — shared complex objects serialized with `id`/`href` per SOAP 1.1 §5.2.5\n- **rpc:result** — opt-in `@soap_operation(emit_rpc_result=True)` per SOAP 1.2 Part 2 §4.2.1\n- WS-Addressing 1.0 — MessageID, RelatesTo, Action, ReferenceParameters propagated in responses\n- XSD type registry with 27 built-in types\n- Sync and async HTTP client (httpx optional)\n- Interoperable with zeep and spyne out-of-the-box (verified by integration tests)\n- **JSON dual-mode** — any SOAP endpoint returns JSON when client sends `Accept: application/json`; no separate endpoint needed\n- **Non-strict WSDL parsing** — `parse_wsdl(..., strict=False)` silently skips unresolvable imports instead of raising\n- Full type annotations + `py.typed` marker (PEP 561)\n- Python 3.10 – 3.14\n\n---\n\n## Installation\n\n```bash\npip install soapbar              # core + server + WSDL (lxml only)\npip install soapbar[core]        # explicit alias for the above\npip install soapbar[server]      # explicit alias for the above\npip install soapbar[client]      # + httpx for the HTTP client\npip install soapbar[security]    # + signxml + cryptography (XML Sig/Enc)\npip install soapbar[all]         # everything (client + security)\n```\n\nOr with uv:\n\n```bash\nuv add soapbar\nuv add \"soapbar[client]\"\nuv add \"soapbar[security]\"\nuv add \"soapbar[all]\"\n```\n\n---\n\n## Quick start — server\n\n### Variant A — standalone (bare ASGI, no framework)\n\n```python\n# app.py\nfrom soapbar import SoapService, soap_operation, SoapApplication, AsgiSoapApp\n\n\nclass CalculatorService(SoapService):\n    __service_name__ = \"Calculator\"\n    __tns__ = \"http://example.com/calculator\"\n\n    @soap_operation()\n    def add(self, a: int, b: int) -\u003e int:\n        return a + b\n\n    @soap_operation()\n    def subtract(self, a: int, b: int) -\u003e int:\n        return a - b\n\n\nsoap_app = SoapApplication(service_url=\"http://localhost:8000\")\nsoap_app.register(CalculatorService())\n\napp = AsgiSoapApp(soap_app)\n# Run: uvicorn app:app --port 8000\n# WSDL: GET http://localhost:8000?wsdl\n```\n\n### Variant B — mounted inside FastAPI\n\n```python\nfrom fastapi import FastAPI\nfrom soapbar import SoapApplication, AsgiSoapApp\n\n# ... (same CalculatorService class as above) ...\n\nsoap_app = SoapApplication(service_url=\"http://localhost:8000/soap\")\nsoap_app.register(CalculatorService())\n\napi = FastAPI()\napi.mount(\"/soap\", AsgiSoapApp(soap_app))\n# Run: uvicorn app:api --port 8000\n# WSDL: GET http://localhost:8000/soap?wsdl\n```\n\n---\n\n## Binding styles and SOAP encoding\n\n### Background — two dimensions\n\nThe WSDL `\u003cbinding\u003e` element is described by two orthogonal choices:\n\n- **Style:** `rpc` or `document` — controls whether the SOAP Body contains a wrapper element named after the operation (`rpc`) or raw parameter elements without a wrapper (`document`).\n- **Use:** `encoded` or `literal` — controls whether each element carries a `xsi:type` attribute with runtime type information (`encoded`) or relies solely on the schema (`literal`).\n\nReferences:\n- [IBM developerWorks — Which WSDL style?](https://developer.ibm.com/articles/ws-whichwsdl/)\n- [DZone — Different SOAP encoding styles](https://dzone.com/articles/different-soap-encoding-styles)\n- [Stack Overflow — Document vs RPC style](https://stackoverflow.com/questions/9062475/what-is-the-difference-between-document-style-and-rpc-style-communication)\n\n### The five combinations\n\n`BindingStyle` is importable as `from soapbar import BindingStyle`.\n\n| `BindingStyle` enum | WSDL style | WSDL use | WS-I BP | Notes |\n|---|---|---|---|---|\n| `RPC_ENCODED` | rpc | encoded | ✗ | Legacy; params carry `xsi:type`; operation wrapper in Body |\n| `RPC_LITERAL` | rpc | literal | ✓ | No `xsi:type`; operation wrapper in Body |\n| `DOCUMENT_LITERAL` | document | literal | ✓ | Params are direct Body children; no wrapper |\n| `DOCUMENT_LITERAL_WRAPPED` | document | literal | ✓ | **Default \u0026 recommended**; single wrapper element named after operation |\n| `DOCUMENT_ENCODED` | document | encoded | ✗ | Params are direct Body children each with `xsi:type` |\n\n#### RPC_ENCODED\n\n```xml\n\u003csoapenv:Body\u003e\n  \u003ctns:add soapenc:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"\u003e\n    \u003ca xsi:type=\"xsd:int\"\u003e3\u003c/a\u003e\n    \u003cb xsi:type=\"xsd:int\"\u003e5\u003c/b\u003e\n  \u003c/tns:add\u003e\n\u003c/soapenv:Body\u003e\n```\n\n#### RPC_LITERAL\n\n```xml\n\u003csoapenv:Body\u003e\n  \u003ctns:add\u003e\n    \u003ca\u003e3\u003c/a\u003e\n    \u003cb\u003e5\u003c/b\u003e\n  \u003c/tns:add\u003e\n\u003c/soapenv:Body\u003e\n```\n\n#### DOCUMENT_LITERAL\n\n```xml\n\u003csoapenv:Body\u003e\n  \u003ca\u003e3\u003c/a\u003e\n  \u003cb\u003e5\u003c/b\u003e\n\u003c/soapenv:Body\u003e\n```\n\n#### DOCUMENT_LITERAL_WRAPPED (default)\n\n```xml\n\u003csoapenv:Body\u003e\n  \u003ctns:add\u003e\n    \u003ca\u003e3\u003c/a\u003e\n    \u003cb\u003e5\u003c/b\u003e\n  \u003c/tns:add\u003e\n\u003c/soapenv:Body\u003e\n```\n\n#### DOCUMENT_ENCODED\n\n```xml\n\u003csoapenv:Body\u003e\n  \u003ca xsi:type=\"xsd:int\"\u003e3\u003c/a\u003e\n  \u003cb xsi:type=\"xsd:int\"\u003e5\u003c/b\u003e\n\u003c/soapenv:Body\u003e\n```\n\n### Which to choose?\n\nUse `DOCUMENT_LITERAL_WRAPPED` unless you are interoperating with a legacy system that requires `RPC_ENCODED`. `DOCUMENT_LITERAL_WRAPPED` is WS-I Basic Profile compliant, the most widely supported style, and the easiest to validate with schema tools.\n\n---\n\n## Defining a service\n\n```python\nfrom decimal import Decimal\nfrom soapbar import SoapService, soap_operation, BindingStyle, SoapVersion, xsd\nfrom soapbar import OperationParameter\n\n\nclass PricingService(SoapService):\n    # Class attributes (all have defaults — only override what you need)\n    __service_name__ = \"Pricing\"\n    __tns__ = \"http://example.com/pricing\"\n    __binding_style__ = BindingStyle.DOCUMENT_LITERAL_WRAPPED\n    __soap_version__ = SoapVersion.SOAP_11\n    __service_url__ = \"http://localhost:8000/soap\"\n\n    # Auto-introspection: input/output params derived from type hints\n    @soap_operation(documentation=\"Calculate discounted price\")\n    def get_price(self, item_id: str, quantity: int) -\u003e Decimal:\n        return Decimal(\"9.99\") * quantity\n\n    # Explicit params: use when hints are insufficient or unavailable\n    @soap_operation(\n        input_params=[\n            OperationParameter(name=\"item_id\", xsd_type=xsd.resolve(\"string\")),\n            OperationParameter(name=\"quantity\", xsd_type=xsd.resolve(\"int\")),\n        ],\n        output_params=[\n            OperationParameter(name=\"price\", xsd_type=xsd.resolve(\"decimal\")),\n        ],\n    )\n    def get_price_explicit(self, item_id: str, quantity: int) -\u003e Decimal:\n        return Decimal(\"9.99\") * quantity\n```\n\n### `SoapService` class attribute defaults\n\n| Attribute | Default | Notes |\n|---|---|---|\n| `__service_name__` | class name | Used in WSDL `\u003cservice name=\"\"\u003e` |\n| `__tns__` | `\"http://example.com/{name}\"` | Target namespace |\n| `__binding_style__` | `BindingStyle.DOCUMENT_LITERAL_WRAPPED` | Recommended default |\n| `__soap_version__` | `SoapVersion.SOAP_11` | Change to `SOAP_12` if needed |\n| `__port_name__` | `\"{name}Port\"` | WSDL port name |\n| `__service_url__` | `\"\"` | Override or pass to `SoapApplication` |\n\n---\n\n## SOAP versions\n\n| | SOAP 1.1 | SOAP 1.2 |\n|---|---|---|\n| Envelope namespace | `http://schemas.xmlsoap.org/soap/envelope/` | `http://www.w3.org/2003/05/soap-envelope` |\n| Content-Type | `text/xml; charset=utf-8` | `application/soap+xml; charset=utf-8` |\n| Action header | `SOAPAction: \"...\"` (separate header) | `action=\"...\"` in Content-Type |\n| Fault code (client) | `Client` | `Sender` |\n| Fault code (server) | `Server` | `Receiver` |\n\nsoapbar detects the SOAP version automatically from the envelope namespace and translates fault codes between versions when building responses.\n\n```python\nfrom soapbar import SoapVersion\n\nSoapVersion.SOAP_11   # SOAP 1.1\nSoapVersion.SOAP_12   # SOAP 1.2\n```\n\n---\n\n## Framework compatibility\n\n### ASGI frameworks (via `AsgiSoapApp`)\n\n`AsgiSoapApp` is a standard ASGI application. Mount it anywhere an ASGI app is accepted.\n\n| Framework | How to mount |\n|---|---|\n| **FastAPI** | `app.mount(\"/soap\", AsgiSoapApp(soap_app))` |\n| **Starlette** | `routes=[Mount(\"/soap\", app=AsgiSoapApp(soap_app))]` |\n| **Litestar** | `app.mount(\"/soap\", AsgiSoapApp(soap_app))` |\n| **Quart** | Use `asgiref` or serve directly with Hypercorn |\n| **BlackSheep** | `app.mount(\"/soap\", AsgiSoapApp(soap_app))` |\n| **Django** (≥ 3.1 ASGI) | Route in `asgi.py` via URL dispatcher |\n\nASGI servers (Uvicorn, Hypercorn, Daphne) can run `AsgiSoapApp` directly.\n\n**FastAPI example:**\n\n```python\nfrom fastapi import FastAPI\nfrom soapbar import SoapApplication, AsgiSoapApp\n\nsoap_app = SoapApplication(service_url=\"http://localhost:8000/soap\")\nsoap_app.register(CalculatorService())\n\napi = FastAPI()\napi.mount(\"/soap\", AsgiSoapApp(soap_app))\n```\n\n### WSGI frameworks (via `WsgiSoapApp`)\n\n| Framework | How to mount |\n|---|---|\n| **Flask** | `DispatcherMiddleware` or replace `app.wsgi_app` (requires `werkzeug`) |\n| **Django** (classic WSGI) | Mount as sub-application in `urls.py` |\n| **Falcon** | `app.add_sink(WsgiSoapApp(soap_app), \"/soap\")` |\n| **Bottle** | `app.mount(\"/soap\", WsgiSoapApp(soap_app))` |\n| **Pyramid** | Composable WSGI stack |\n\nWSGI servers (Gunicorn, uWSGI, mod_wsgi) can run `WsgiSoapApp` directly.\n\n**Flask example:**\n\n```python\nfrom flask import Flask\nfrom werkzeug.middleware.dispatcher import DispatcherMiddleware\nfrom soapbar import SoapApplication, WsgiSoapApp\n\nsoap_app = SoapApplication(service_url=\"http://localhost:8000/soap\")\nsoap_app.register(CalculatorService())\n\nflask_app = Flask(__name__)\nflask_app.wsgi_app = DispatcherMiddleware(flask_app.wsgi_app, {\n    \"/soap\": WsgiSoapApp(soap_app),\n})\n```\n\n---\n\n## WSDL\n\n**Auto-generation** — no configuration needed. Register a service and the WSDL is generated automatically:\n\n```python\nwsdl_bytes = soap_app.get_wsdl()\n```\n\nServed automatically at `GET ?wsdl` when using `AsgiSoapApp` or `WsgiSoapApp`.\n\n**Parse an existing WSDL** to inspect its structure:\n\n```python\nfrom soapbar import parse_wsdl, parse_wsdl_file\n\ndefn = parse_wsdl(wsdl_bytes)          # from bytes/str\ndefn = parse_wsdl_file(\"service.wsdl\") # from file\n```\n\n**Custom WSDL override** — supply your own WSDL document and skip auto-generation:\n\n```python\nsoap_app = SoapApplication(custom_wsdl=open(\"my_service.wsdl\", \"rb\").read())\n```\n\n**Remote `wsdl:import` — SSRF guard** — `parse_wsdl` blocks outbound HTTP fetches by default. `wsdl:import` elements whose resolved location starts with `http://` or `https://` raise `ValueError` unless you explicitly opt in:\n\n```python\n# Default — safe for untrusted WSDLs; remote imports raise ValueError\ndefn = parse_wsdl(wsdl_bytes)\n\n# Opt-in — only when the WSDL source is trusted\ndefn = parse_wsdl(wsdl_bytes, allow_remote_imports=True)\n```\n\nThis prevents Server-Side Request Forgery (SSRF) when parsing WSDLs from user-supplied URLs or untrusted data. The top-level WSDL fetch (e.g. `SoapClient(wsdl_url=...)`) is always explicit; only `wsdl:import` resolution inside the document is guarded.\n\n---\n\n## Client\n\n```python\nimport asyncio\nfrom soapbar import SoapClient, SoapFault\n\n# From a live WSDL URL (fetches WSDL over HTTP)\nclient = SoapClient(wsdl_url=\"http://localhost:8000/soap?wsdl\")\n\n# From a WSDL string/bytes you already have\nclient = SoapClient.from_wsdl_string(wsdl_bytes)\n\n# From a WSDL file\nclient = SoapClient.from_file(\"service.wsdl\")\n\n# Manual — no WSDL, specify endpoint and style directly\nfrom soapbar import BindingStyle, SoapVersion\n\nclient = SoapClient.manual(\n    address=\"http://localhost:8000/soap\",\n    binding_style=BindingStyle.DOCUMENT_LITERAL_WRAPPED,\n    soap_version=SoapVersion.SOAP_11,\n)\n\n# Sync call via service proxy\ntry:\n    result = client.service.add(a=3, b=5)\n    print(result)  # 8\nexcept SoapFault as fault:\n    print(fault.faultcode, fault.faultstring)\n\n# Direct call by operation name\nresult = client.call(\"add\", a=3, b=5)\n\n# Async call\nasync def main():\n    result = await client.call_async(\"add\", a=3, b=5)\n    print(result)\n\nasyncio.run(main())\n```\n\n### `HttpTransport` options\n\n```python\nfrom soapbar import SoapClient, HttpTransport\n\ntransport = HttpTransport(timeout=60.0, verify_ssl=False)\nclient = SoapClient(wsdl_url=\"http://localhost:8000/soap?wsdl\", transport=transport)\n```\n\n#### Mutual TLS (client certificate)\n\nServices behind a private or government PKI require the client to present a\ncertificate on the TLS handshake, and often to verify the server against a\ncustom CA. Pass `client_cert` (a combined-PEM path, a `(certfile, keyfile)`\ntuple, or in-memory `(cert_pem, key_pem)` bytes) and `ca_bundle`:\n\n```python\nfrom soapbar import HttpTransport, load_pkcs12\n\n# From PEM files on disk:\ntransport = HttpTransport(\n    client_cert=(\"client.pem\", \"client.key\"),\n    ca_bundle=\"private-ca.pem\",\n)\n\n# Or from a PKCS#12 (.pfx) bundle (e.g. an ICP-Brasil A1 certificate) — the\n# private key stays in memory and is never written to disk:\ncert_pem, key_pem = load_pkcs12(\"certificate.pfx\", \"password\")\ntransport = HttpTransport(client_cert=(cert_pem, key_pem), ca_bundle=\"private-ca.pem\")\n```\n\nMutual TLS requires httpx (`soapbar[client]`); `load_pkcs12` requires\n`cryptography` (`soapbar[security]`).\n\n#### Session cookies\n\nStateful services keep a session across calls via cookies (e.g. a login that\nreturns `JSESSIONID`). When a transport is reused, its cookie jar persists, so\nthe session is carried automatically. Read or inject cookies via\n`transport.cookies`:\n\n```python\ntransport = HttpTransport()  # persist_cookies=True by default\nclient = SoapClient(wsdl_url=\"https://service/?wsdl\", transport=transport)\n\nclient.call(\"Login\", user=\"...\", password=\"...\")   # server sets JSESSIONID\nprint(transport.cookies.get(\"JSESSIONID\"))          # read it\nclient.call(\"DoWork\", ...)                          # cookie sent automatically\nclient.call(\"Logout\")\n\n# Or inject a session cookie obtained out of band:\ntransport.cookies.set(\"JSESSIONID\", \"abc123\", domain=\"service\")\n```\n\nPass `HttpTransport(persist_cookies=False)` for stateless behaviour — the jar\nis cleared after every call. Session cookies require httpx (`soapbar[client]`).\n\n### Advanced: manual client with explicit operation signature\n\nUse `register_operation` when you need full control over the operation schema without a WSDL:\n\n```python\nfrom soapbar import SoapClient, OperationSignature, OperationParameter, BindingStyle, xsd\n\nsig = OperationSignature(\n    name=\"Add\",\n    input_params=[\n        OperationParameter(\"a\", xsd.resolve(\"int\")),\n        OperationParameter(\"b\", xsd.resolve(\"int\")),\n    ],\n    output_params=[OperationParameter(\"return\", xsd.resolve(\"int\"))],\n)\n\nclient = SoapClient.manual(\"http://host/soap\", binding_style=BindingStyle.RPC_LITERAL)\nclient.register_operation(sig)\nresult = client.call(\"Add\", a=3, b=4)  # 7\n```\n\n---\n\n## XSD type system\n\nsoapbar includes a registry of 27 built-in XSD types. Types handle serialization to and from XML text.\n\n```python\nfrom soapbar import xsd\n\n# Resolve a type by XSD name\nint_type = xsd.resolve(\"int\")        # XsdType for xsd:int\nstr_type = xsd.resolve(\"string\")     # XsdType for xsd:string\n\n# Map a Python type to its XSD equivalent\nxsd_type = xsd.python_to_xsd(int)    # -\u003e xsd:int XsdType\nxsd_type = xsd.python_to_xsd(str)    # -\u003e xsd:string XsdType\n\n# Serialize / deserialize\nint_type.to_xml(42)       # \"42\"\nint_type.from_xml(\"42\")   # 42\n\n# Inspect all registered types\nall_types = xsd.all_types()\n```\n\nPython → XSD mapping:\n\n| Python type | XSD type |\n|---|---|\n| `bool` | `boolean` |\n| `int` | `int` |\n| `float` | `float` |\n| `str` | `string` |\n| `Decimal` | `decimal` |\n| `bytes` | `base64Binary` |\n\n---\n\n## Fault handling\n\n### Raising a fault from a service method\n\n```python\nfrom soapbar import SoapService, soap_operation, SoapFault\n\n\nclass StrictCalculator(SoapService):\n    __service_name__ = \"StrictCalculator\"\n    __tns__ = \"http://example.com/calc\"\n\n    @soap_operation()\n    def divide(self, a: int, b: int) -\u003e int:\n        if b == 0:\n            raise SoapFault(\n                faultcode=\"Client\",\n                faultstring=\"Division by zero\",\n                detail=\"b must be non-zero\",\n            )\n        return a // b\n```\n\n`SoapClient.call()` and `call_async()` automatically raise `SoapFault` when the server returns a fault response.\n\n### Creating and rendering faults manually\n\n```python\nfrom soapbar import SoapFault\n\n# Create a fault\nfault = SoapFault(\n    faultcode=\"Client\",\n    faultstring=\"Invalid input: quantity must be positive\",\n    detail=\"quantity=-1\",          # string or lxml _Element\n)\n\n# Render as SOAP 1.1 or 1.2 envelope\nenvelope_11 = fault.to_soap11_envelope()\nenvelope_12 = fault.to_soap12_envelope()\n\n# SOAP 1.2 subcodes — each is (namespace_uri, localname) for spec-compliant QName\nfault_12 = SoapFault(\n    faultcode=\"Client\",\n    faultstring=\"Validation error\",\n    subcodes=[(\"http://example.com/errors\", \"InvalidQuantity\")],\n)\n```\n\nFault code translation is automatic:\n\n| Canonical (used in soapbar) | SOAP 1.1 wire | SOAP 1.2 wire |\n|---|---|---|\n| `Client` | `Client` | `Sender` |\n| `Server` | `Server` | `Receiver` |\n\n---\n\n## Security\n\nsoapbar uses a hardened lxml parser:\n\n```python\nlxml.etree.XMLParser(\n    resolve_entities=False,   # XXE prevention\n    no_network=True,          # SSRF prevention\n    load_dtd=False,           # DTD injection prevention\n    huge_tree=False,          # Billion-Laughs prevention\n    remove_comments=True,     # comment injection prevention\n    remove_pis=True,\n)\n```\n\nEntity references (potential XXE payloads) are silently dropped rather than expanded. No network connections are made during parsing. DTDs are not loaded.\n\nAdditional hardening:\n- **Message size limit**: `SoapApplication(max_body_size=10*1024*1024)` — requests exceeding 10 MB are rejected with a `Client` fault before XML parsing.\n- **XML nesting depth**: requests exceeding 100 levels of nesting are rejected to prevent stack exhaustion.\n- **Error scrubbing**: unhandled exceptions produce `\"An internal error occurred.\"` — no stack traces or exception text are returned to clients.\n- **HTTPS warning**: `SoapApplication` warns at construction time if `service_url` uses plain HTTP.\n\n---\n\n## WS-Security — UsernameToken\n\nsoapbar supports WS-Security 1.0 UsernameToken (OASIS 2004), both plain-text and SHA-1 digest.\n\n### Client — attaching credentials\n\n```python\nfrom soapbar import SoapClient\nfrom soapbar.core.wssecurity import UsernameTokenCredential\n\n# Plain-text password\ncred = UsernameTokenCredential(username=\"alice\", password=\"secret\")\n\n# SHA-1 PasswordDigest (recommended for non-TLS scenarios)\ncred = UsernameTokenCredential(username=\"alice\", password=\"secret\", use_digest=True)\n\nclient = SoapClient.manual(\n    \"https://example.com/soap\",\n    wss_credential=cred,\n)\nresult = client.call(\"GetData\", id=42)\n```\n\nThe `wsse:Security` header is injected automatically on every call.\n\n### Server — validating credentials\n\n```python\nfrom soapbar import SoapApplication\nfrom soapbar.core.wssecurity import UsernameTokenValidator, SecurityValidationError\n\n\nclass MyValidator(UsernameTokenValidator):\n    _users = {\"alice\": \"secret\", \"bob\": \"hunter2\"}\n\n    def get_password(self, username: str) -\u003e str | None:\n        return self._users.get(username)\n\n\napp = SoapApplication(\n    service_url=\"https://example.com/soap\",\n    security_validator=MyValidator(),\n)\napp.register(MyService())\n```\n\n`SecurityValidationError` is converted to a `Client` SOAP fault automatically. Both PasswordText and PasswordDigest token types are verified; Digest requires `wsse:Nonce` and `wsu:Created` to be present.\n\n---\n\n## MTOM/XOP\n\nsoapbar supports MTOM (Message Transmission Optimization Mechanism, W3C) for sending and receiving SOAP messages with binary attachments. The `multipart/related` MIME packaging is handled transparently — the core envelope sees resolved base64 data; your service code sees plain bytes.\n\n### Client — sending attachments\n\n```python\nfrom soapbar import SoapClient, BindingStyle\n\nclient = SoapClient.manual(\n    \"http://localhost:8000/soap\",\n    binding_style=BindingStyle.DOCUMENT_LITERAL_WRAPPED,\n    use_mtom=True,\n)\n\n# Queue a binary attachment and get its Content-ID back\ncid = client.add_attachment(b\"\\x89PNG...\", content_type=\"image/png\")\n\n# The call packages the envelope + attachments as multipart/related\nresult = client.call(\"UploadImage\", image_cid=cid, filename=\"logo.png\")\n```\n\n### Server — receiving MTOM\n\nNo configuration required. `AsgiSoapApp` and `WsgiSoapApp` automatically detect inbound `multipart/related` requests, resolve all `xop:Include` references inline, and pass the reconstructed XML to the dispatcher as a normal SOAP envelope.\n\n### Low-level API\n\n```python\nfrom soapbar import parse_mtom, build_mtom, MtomAttachment\n\n# Parse a raw MTOM HTTP body\nmsg = parse_mtom(raw_bytes, content_type_header)\nprint(msg.soap_xml)       # bytes — envelope with XOP includes resolved\nprint(msg.attachments)    # list[MtomAttachment]\n\n# Build a MTOM HTTP body\nattachments = [MtomAttachment(content_id=\"part1@host\", content_type=\"image/png\", data=png_bytes)]\nbody_bytes, content_type = build_mtom(soap_xml_bytes, attachments)\n```\n\n---\n\n## XML Signature and Encryption\n\nRequires `pip install soapbar[security]` (pulls in `signxml` and `cryptography`).\n\n### XML Digital Signature (XML-DSIG)\n\n```python\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography.hazmat.primitives import hashes\nfrom cryptography.x509 import CertificateBuilder\nfrom soapbar.core.wssecurity import sign_envelope, verify_envelope, XmlSecurityError\n\n# Sign — enveloped RSA-SHA256 XML-DSIG\nsigned_bytes = sign_envelope(envelope_bytes, private_key, certificate)\n\n# Verify — raises XmlSecurityError on bad signature\ntry:\n    verified_bytes = verify_envelope(signed_bytes, certificate)\nexcept XmlSecurityError as exc:\n    print(\"Signature invalid:\", exc)\n```\n\n#### Signing an internal element by `Id`\n\nSome services sign an inner element selected by its `Id` (referenced as\n`#\u003cid\u003e`) rather than the whole envelope — most notably SEFAZ NF-e, which signs\n`\u003cinfNFe\u003e`. `sign_element_by_id` does exactly that, with a single\n`ds:Reference` and an enveloped signature:\n\n```python\nfrom soapbar.core.wssecurity import sign_element_by_id\n\n# Defaults: RSA-SHA256 / SHA-256 / Exclusive C14N.\nsigned = sign_element_by_id(nfe_xml, \"NFe3106...\", private_key, certificate)\n\n# SEFAZ NF-e mandates the legacy algorithm set:\nsigned = sign_element_by_id(\n    nfe_xml,\n    \"NFe3106...\",            # the \u003cinfNFe Id=\"...\"\u003e value\n    private_key,\n    certificate,\n    signature_method=\"rsa-sha1\",\n    digest_method=\"sha1\",\n    c14n=\"inclusive\",        # http://www.w3.org/TR/2001/REC-xml-c14n-20010315\n    end_cert_only=True,      # only the end-entity cert in KeyInfo\n)\n```\n\n### XML Encryption (AES-256-CBC + RSA-OAEP)\n\n```python\nfrom soapbar.core.wssecurity import encrypt_body, decrypt_body, XmlSecurityError\n\n# Encrypt SOAP Body — AES-256-CBC session key wrapped with recipient's RSA public key\nencrypted_bytes = encrypt_body(envelope_bytes, recipient_public_key)\n\n# Decrypt — extracts and unwraps the session key, restores Body children\ndecrypted_bytes = decrypt_body(encrypted_bytes, recipient_private_key)\n```\n\nThe `xenc:EncryptedData` element is placed as the sole child of `\u003csoap:Body\u003e`. The AES-256-CBC session key is wrapped with RSA-OAEP (SHA-256) in an `xenc:EncryptedKey` element inside `xenc:KeyInfo`.\n\n### WS-I BSP X.509 Token Profile (S10)\n\nFor interoperability with WS-I Basic Security Profile 1.1 compliant clients and servers, use the BSP variant which embeds the certificate as a `wsse:BinarySecurityToken` and references it from `ds:Signature/ds:KeyInfo`:\n\n```python\nfrom soapbar.core.wssecurity import (\n    sign_envelope_bsp,\n    verify_envelope_bsp,\n    build_binary_security_token,\n    extract_certificate_from_security,\n)\n\n# Sign — adds wsse:BinarySecurityToken + wsse:SecurityTokenReference in KeyInfo\nsigned_bytes = sign_envelope_bsp(envelope_bytes, private_key, certificate)\n\n# Verify — extracts cert from BST, verifies ds:Signature\nverified_bytes = verify_envelope_bsp(signed_bytes)\n\n# Build a standalone BinarySecurityToken element (e.g. to add to an existing header)\nbst = build_binary_security_token(certificate, token_id=\"MyToken-1\")\n```\n\n---\n\n## WSDL schema validation\n\n`SoapApplication` can validate the SOAP Body of each inbound request against the XSD types embedded in the WSDL. Validation is opt-in and disabled by default.\n\n```python\nfrom soapbar import SoapApplication\n\nsoap_app = SoapApplication(\n    service_url=\"https://example.com/soap\",\n    validate_body_schema=True,   # X07 — WS-I BP 1.1 R2201\n)\nsoap_app.register(MyService())\n```\n\nWhen enabled, the compiled `lxml.etree.XMLSchema` is built once from the WSDL-embedded `\u003cxs:schema\u003e` elements and cached. Any Body element that fails schema validation results in a `Client` fault with the first schema error message. Requests to services with no embedded schemas pass through unchanged.\n\n---\n\n## One-way operations\n\nOne-way operations fire-and-forget: the server processes the message and returns HTTP 202 Accepted with an empty body (SOAP 1.2 Part 2 §7.5.1).\n\n```python\nfrom soapbar import SoapService, soap_operation\n\n\nclass EventService(SoapService):\n    __service_name__ = \"EventService\"\n    __tns__ = \"http://example.com/events\"\n\n    @soap_operation(one_way=True)\n    def publish_event(self, event_type: str, payload: str) -\u003e None:\n        # Process asynchronously — no response is sent\n        _event_queue.put((event_type, payload))\n```\n\nThe client receives `202 Accepted` with no body. `SoapClient.call()` returns `None` for one-way operations.\n\n---\n\n## SOAP array attributes\n\nWhen using encoded binding styles (`RPC_ENCODED`, `DOCUMENT_ENCODED`), array elements are annotated with the correct version-specific attributes automatically.\n\nSOAP 1.1 (`SOAP-ENC:arrayType`):\n```xml\n\u003cnames soapenc:arrayType=\"xsd:string[3]\"\n       xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"\u003e\n  \u003citem\u003eAlice\u003c/item\u003e\u003citem\u003eBob\u003c/item\u003e\u003citem\u003eCarol\u003c/item\u003e\n\u003c/names\u003e\n```\n\nSOAP 1.2 (`enc:itemType` + `enc:arraySize`):\n```xml\n\u003cnames enc:itemType=\"xsd:string\" enc:arraySize=\"3\"\n       xmlns:enc=\"http://www.w3.org/2003/05/soap-encoding\"\u003e\n  \u003citem\u003eAlice\u003c/item\u003e\u003citem\u003eBob\u003c/item\u003e\u003citem\u003eCarol\u003c/item\u003e\n\u003c/names\u003e\n```\n\nThe correct attributes are emitted automatically based on the SOAP version in use — no manual configuration needed. The `get_serializer(style, soap_version)` factory handles the selection.\n\n---\n\n## rpc:result (SOAP 1.2)\n\nSOAP 1.2 Part 2 §4.2.1 defines a `rpc:result` SHOULD convention for naming the return value in RPC responses. soapbar omits it by default (preserving interoperability with zeep and other strict-mode clients) and offers an opt-in:\n\n```python\nfrom soapbar import SoapService, soap_operation\n\n\nclass CalcService(SoapService):\n    __service_name__ = \"Calc\"\n    __tns__ = \"http://example.com/calc\"\n\n    # Default: no rpc:result (interoperable with zeep, WCF, etc.)\n    @soap_operation()\n    def add(self, a: int, b: int) -\u003e int:\n        return a + b\n\n    # Opt-in: emit rpc:result for strict SOAP 1.2 consumers\n    @soap_operation(emit_rpc_result=True)\n    def add_strict(self, a: int, b: int) -\u003e int:\n        return a + b\n```\n\nWhen opted in, the response wrapper contains:\n```xml\n\u003cCalcResponse\u003e\n  \u003crpc:result xmlns:rpc=\"http://www.w3.org/2003/05/soap-rpc\"\u003ereturn\u003c/rpc:result\u003e\n  \u003creturn\u003e8\u003c/return\u003e\n\u003c/CalcResponse\u003e\n```\n\n---\n\n## Interoperability\n\nsoapbar is tested against zeep and spyne via integration tests.\n\n- **zeep → soapbar**: a zeep client can call a soapbar server without modification. The WSDL generated by soapbar is zeep-parseable.\n- **soapbar → spyne**: a soapbar client can call a spyne server using RPC/Literal.\n- **soapbar ↔ soapbar**: full round-trip tested for all binding styles and both SOAP versions.\n\n---\n\n## Real-world services\n\nsoapbar is exercised against actual government/industry SOAP services. Runnable\ndemonstrations live under [`examples/`](examples/); maintained, independently\nversioned client packages (depending on soapbar) are planned as separate\nprojects.\n\n| Service | Binding | Auth | Example | Client package |\n|---|---|---|---|---|\n| EU VIES (VAT validation) | document/literal, SOAP 1.1 | none | [`17_vies/`](examples/17_vies/) | `soapbar-vies` *(planned)* |\n| WITSML 1.4.1.1 STORE | RPC | WS-Security UsernameToken | [`18_witsml/`](examples/18_witsml/) | `soapbar-witsml` *(planned)* |\n| SEFAZ NF-e | document/literal, SOAP 1.2 | mutual TLS (ICP-Brasil) + `\u003cinfNFe\u003e` `Id`-signing | [`19_nfe/`](examples/19_nfe/) | `soapbar-nfe` *(planned)* |\n| IRS MeF (A2A) | SOAP/HTTP, session-based | mutual TLS (Strong Auth) + session cookies | [`20_mef/`](examples/20_mef/) | `soapbar-mef` *(planned)* |\n\nThe VIES and WITSML examples run against live endpoints; the NF-e and MeF\nexamples are faithful references (their `main()` prints guidance without\nnetwork access). The maintained client packages will carry domain models,\ntyped faults, and gated live integration tests (`pytest -m live`).\n\n---\n\n## Architecture\n\n```\n  HTTP request\n       │\n       ▼\n┌─────────────────┐\n│ AsgiSoapApp /   │   ← thin ASGI/WSGI adapters\n│ WsgiSoapApp     │\n└────────┬────────┘\n         │\n         ▼\n┌─────────────────┐\n│ SoapApplication │   ← dispatcher: version detection,\n│                 │     operation routing, fault wrapping\n└────────┬────────┘\n         │\n         ▼\n┌─────────────────┐\n│  SoapService    │   ← your business logic lives here\n│  @soap_operation│\n└────────┬────────┘\n         │ calls binding serializer + envelope builder\n         ▼\n┌─────────────────┐\n│  core/          │   ← binding.py · envelope.py · types.py\n│  binding/types/ │       wsdl/ · xml.py · fault.py\n│  envelope/wsdl  │\n└─────────────────┘\n```\n\n---\n\n## Public API\n\nThe most-used symbols are all importable from the top-level `soapbar` namespace:\n\n| Symbol | Import | Description |\n|--------|--------|-------------|\n| `SoapService` | `from soapbar import SoapService` | Base class for SOAP services |\n| `soap_operation` | `from soapbar import soap_operation` | Decorator for service methods |\n| `SoapApplication` | `from soapbar import SoapApplication` | SOAP dispatcher/router |\n| `AsgiSoapApp` | `from soapbar import AsgiSoapApp` | ASGI adapter |\n| `WsgiSoapApp` | `from soapbar import WsgiSoapApp` | WSGI adapter |\n| `SoapClient` | `from soapbar import SoapClient` | SOAP client |\n| `HttpTransport` | `from soapbar import HttpTransport` | HTTP transport layer |\n| `SoapFault` | `from soapbar import SoapFault` | SOAP fault exception |\n| `BindingStyle` | `from soapbar import BindingStyle` | Binding style enum |\n| `SoapVersion` | `from soapbar import SoapVersion` | SOAP version enum |\n| `xsd` | `from soapbar import xsd` | XSD type registry |\n| `parse_wsdl` | `from soapbar import parse_wsdl` | Parse WSDL from bytes/str |\n| `parse_wsdl_file` | `from soapbar import parse_wsdl_file` | Parse WSDL from a file path |\n| `build_wsdl_string` | `from soapbar import build_wsdl_string` | Generate WSDL as string |\n| `OperationParameter` | `from soapbar import OperationParameter` | Parameter descriptor for operations |\n| `OperationSignature` | `from soapbar import OperationSignature` | Full operation signature (manual client) |\n| `UsernameTokenCredential` | `from soapbar.core.wssecurity import UsernameTokenCredential` | WS-Security credential for client |\n| `UsernameTokenValidator` | `from soapbar.core.wssecurity import UsernameTokenValidator` | Abstract base for server-side token validation |\n| `SecurityValidationError` | `from soapbar.core.wssecurity import SecurityValidationError` | Raised on authentication failure |\n| `build_security_header` | `from soapbar.core.wssecurity import build_security_header` | Build `wsse:Security` header element |\n| `sign_envelope` | `from soapbar.core.wssecurity import sign_envelope` | Enveloped XML-DSIG signature (RSA-SHA256) |\n| `verify_envelope` | `from soapbar.core.wssecurity import verify_envelope` | Verify and return signed envelope bytes |\n| `encrypt_body` | `from soapbar.core.wssecurity import encrypt_body` | AES-256-CBC body encryption + RSA-OAEP key wrap |\n| `decrypt_body` | `from soapbar.core.wssecurity import decrypt_body` | Decrypt `xenc:EncryptedData` body and restore children |\n| `XmlSecurityError` | `from soapbar.core.wssecurity import XmlSecurityError` | Raised on XML signature/encryption failure |\n| `build_binary_security_token` | `from soapbar.core.wssecurity import build_binary_security_token` | Build WS-I BSP `wsse:BinarySecurityToken` from X.509 cert |\n| `extract_certificate_from_security` | `from soapbar.core.wssecurity import extract_certificate_from_security` | Extract X.509 cert from `wsse:BinarySecurityToken` |\n| `sign_envelope_bsp` | `from soapbar.core.wssecurity import sign_envelope_bsp` | BSP-compliant signing with `wsse:SecurityTokenReference` |\n| `verify_envelope_bsp` | `from soapbar.core.wssecurity import verify_envelope_bsp` | Verify BSP-signed envelope using embedded BST cert |\n| `MtomAttachment` | `from soapbar import MtomAttachment` | MTOM attachment descriptor (content_id, content_type, data) |\n| `MtomMessage` | `from soapbar import MtomMessage` | Parsed MTOM message (soap_xml + attachments list) |\n| `parse_mtom` | `from soapbar import parse_mtom` | Parse a raw `multipart/related` MTOM body |\n| `build_mtom` | `from soapbar import build_mtom` | Build a `multipart/related` MTOM body |\n\n---\n\n## Comparison with alternatives\n\n| Capability | **soapbar** | zeep | spyne | fastapi-soap |\n|---|---|---|---|---|\n| SOAP client | ✓ | ✓ | ✗ | ✗ |\n| SOAP server | ✓ | ✗ | ✓ | ✓ |\n| All 5 binding styles | ✓ | ✓ (client) | ✓ | Partial |\n| SOAP 1.1 + 1.2 | ✓ | ✓ | ✓ | 1.1 only |\n| ASGI frameworks | ✓ | ✗ | ✗ | FastAPI only |\n| WSGI frameworks | ✓ | ✗ | ✓ | ✗ |\n| Auto WSDL generation | ✓ | ✗ | ✓ | ✓ |\n| WSDL-driven client | ✓ | ✓ | ✗ | ✗ |\n| XXE hardened by default | ✓ | undocumented | undocumented | undocumented |\n| Message size + depth limits | ✓ | ✗ | ✗ | ✗ |\n| WS-Security UsernameToken | ✓ | ✓ (client) | ✓ | ✗ |\n| XML Signature / Encryption | ✓ ([security]) | ✗ | Partial | ✗ |\n| MTOM/XOP | ✓ | ✓ | ✓ | ✗ |\n| WS-Addressing 1.0 | ✓ | ✓ | Partial | ✗ |\n| One-way MEP (HTTP 202) | ✓ | ✓ | ✓ | ✗ |\n| SOAP array attributes | ✓ | ✓ | ✓ | ✗ |\n| Internal conformance suite (135 tests) | ✓ | not claimed | not claimed | not claimed |\n| Core dependency | lxml | lxml, requests | lxml | fastapi, lxml |\n| Async HTTP client | httpx (optional) | httpx (optional) | — | — |\n| Python versions | 3.10–3.14 | 3.8+ | 3.8+ | 3.8+ |\n\nsoapbar is the only Python library that covers both client and server, works with any ASGI or WSGI framework, supports SOAP 1.1 and 1.2, is hardened against XXE/DoS attacks out of the box, and ships with an internal conformance suite of 135 tests covering 46 spec-derived checkpoints (see `tests/audit/test_compliance.py`).\n\n---\n\n## Development setup\n\n```bash\ngit clone https://github.com/hitoshyamamoto/soapbar\ncd soapbar\nuv sync --group dev --group lint --group type\n\n# Run tests\nuv run pytest tests/ -v\n\n# Lint\nuv run ruff check src/ tests/\n\n# Type check\nuv run mypy src/\n```\n\nRun the example server (requires FastAPI + uvicorn):\n\n```bash\npip install fastapi uvicorn\nuvicorn examples.calculator_fastapi:app --reload --port 8000\n```\n\nThen fetch the WSDL: `curl http://localhost:8000/soap?wsdl`\n\n---\n\n## Inspired by\n\n- **[Spyne](https://github.com/arskom/spyne)** — the original comprehensive Python SOAP/RPC framework; inspired the service-class model and binding style abstractions.\n- **[zeep](https://github.com/mvantellingen/python-zeep)** — the de facto modern Python SOAP client; inspired the WSDL-driven client approach and XSD type mapping.\n- **[fastapi-soap](https://github.com/rezashahnazar/fastapi-soap)** — demonstrated clean FastAPI/ASGI integration for SOAP endpoints; inspired the ASGI adapter design.\n\n---\n\n## Learn more\n\n**SOAP protocol**\n- [Wikipedia — SOAP](https://pt.wikipedia.org/wiki/SOAP)\n- [W3Schools — XML/SOAP intro](https://www.w3schools.com/XML/)\n- [GeeksForGeeks — Basics of SOAP](https://www.geeksforgeeks.org/computer-networks/basics-of-soap-simple-object-access-protocol/)\n- [Oracle — SOAP API reference](https://docs.oracle.com/en/cloud/saas/applications-common/25a/biacc/soap-api.html)\n\n**WSDL**\n- [TutorialsPoint — WSDL](https://www.tutorialspoint.com/wsdl/index.htm)\n- [GeeksForGeeks — WSDL introduction](https://www.geeksforgeeks.org/software-engineering/wsdl-introduction/)\n\n**Binding styles and encoding**\n- [IBM developerWorks — Which WSDL style?](https://developer.ibm.com/articles/ws-whichwsdl/)\n- [DZone — Different SOAP encoding styles](https://dzone.com/articles/different-soap-encoding-styles)\n- [Stack Overflow — Document vs RPC style](https://stackoverflow.com/questions/9062475/what-is-the-difference-between-document-style-and-rpc-style-communication)\n\n---\n\n## Known Limitations\n\nThe following features are intentionally out-of-scope for the current release.  Behaviour is well-defined in each case (documented exception or graceful exposure).\n\n| Area | Status | Notes |\n|------|--------|-------|\n| **MTOM/XOP** | Fully implemented | `parse_mtom` / `build_mtom` handle `multipart/related` MIME packaging and XOP Include resolution. `AsgiSoapApp` and `WsgiSoapApp` decode inbound MTOM automatically. `SoapClient` sends MTOM when `use_mtom=True`. |\n| **WS-Security** | Fully implemented | `UsernameTokenCredential` / `UsernameTokenValidator` for PasswordText and PasswordDigest. `sign_envelope` / `verify_envelope` for XML-DSIG. `encrypt_body` / `decrypt_body` for XML Encryption (AES-256-CBC + RSA-OAEP). `sign_envelope_bsp` / `verify_envelope_bsp` + `build_binary_security_token` for WS-I BSP X.509 token profile (S10). All require `soapbar[security]`. |\n| **WS-Addressing** | Fully parsed + response headers generated | Inbound headers (`MessageID`, `To`, `Action`, `ReplyTo`, `FaultTo`, `ReferenceParameters`) are parsed into `WsaHeaders`. Response headers (`MessageID`, `RelatesTo`, `Action`, ReferenceParameters) are generated automatically when `use_wsa=True`. |\n| **SOAP 1.2 `relay` attribute** | Parsed and exposed on `SoapHeaderBlock` | The `relay` boolean is available on each `SoapHeaderBlock` instance. Full SOAP intermediary forwarding (actually relaying the message) is not implemented. |\n| **`xsd:complexType` / `xsd:array` / `xsd:choice`** | Fully supported for round-trip serialization | Recursive (`self-referencing`) complex types are resolved lazily. `xsd:complexContent/restriction` for SOAP-encoded arrays is also parsed from WSDL. |\n| **External schema `xsd:import` / `xsd:include`** | Resolved recursively (since 0.6.2) | `wsdl:import` (document-level) and `xsd:import` / `xsd:include` children of `\u003cxsd:schema\u003e` inside `\u003cwsdl:types\u003e` are both resolved, with the same SSRF guard (`allow_remote_imports=False` by default blocks `http(s)://` fetches). Complex types from imported/included schemas are registered alongside inline ones. Circular imports are cycle-detected; depth is capped at 8 levels. |\n| **WS-Addressing reply / fault routing (A04, A05)** | EPRs validated, not routed | `wsa:ReplyTo` and `wsa:FaultTo` Endpoint References are parsed and validated as absolute URIs. Responses and faults are always returned on the request's HTTP back-channel — soapbar does not dispatch outbound HTTP from the server, and the client does not consume EPRs from a peer's response. The well-known constants `WSA_ANONYMOUS` and `WSA_NONE` are exposed in `soapbar.core.envelope` for callers building EPR-aware logic on top of `handle_request()`. See `SECURITY.md` for the full scope note. |\n| **SOAP 1.2 recursive `Subcode`** | Supported on `SoapFault` | `SoapFault(subcodes=[(ns_uri, local_name), …])` emits nested `\u003cenv:Subcode\u003e/\u003cenv:Value\u003e` hierarchy per SOAP 1.2 Part 1 §5.4.1. SOAP 1.1 uses dot-notation (`Client.Authentication`) which is a convention, not a formal grammar, and is not parsed into structured form. |\n| **WSDL 2.0** | Not supported | soapbar generates and parses WSDL 1.1 only. WSDL 2.0 adoption is low outside JAX-WS/Metro; 1.1 remains the de facto industry standard and interoperates cleanly with zeep / spyne / WCF / CXF / WSS4J. |\n| **WS-Policy / WS-PolicyAttachment** | Out of scope | Generated WSDL does not include `\u003cwsp:Policy\u003e` or `\u003cwsp:PolicyReference\u003e` elements. Deployers requiring declarative policy (algorithm suites, transport bindings, token assertions) should run a WS-Policy processor upstream or document the policy out-of-band. WS-SecurityPolicy assertions are likewise not emitted. |\n| **WS-ReliableMessaging / WS-Trust / WS-SecureConversation / WS-Federation** | Out of scope | These WS-* specifications are not implemented. soapbar's WS-Security surface covers UsernameToken, XML Signature, XML Encryption, Timestamp, and the BSP X.509 token profile — sufficient for the audit-checkpoint matrix items F01–S10 but not the token-issuance / session-continuity / federation specs. |\n\n---\n\n## License\n\nApache License 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhitoshyamamoto%2Fsoapbar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhitoshyamamoto%2Fsoapbar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhitoshyamamoto%2Fsoapbar/lists"}