Skip to content

WSDL

Auto-generation — no configuration needed. Register a service and the WSDL is generated automatically:

wsdl_bytes = soap_app.get_wsdl()

Served automatically at GET ?wsdl when using AsgiSoapApp or WsgiSoapApp.

For programmatic generation, build_wsdl(defn, address) returns the wsdl:definitions element for a WsdlDefinition and build_wsdl_string its serialized form; the NS class exposes the namespace URI constants (NS.WSDL, NS.XSD, NS.SOAP_ENV, ...) used throughout soapbar.

Applications spanning several namespaces

A SoapApplication can serve any number of services. Whenever they share a __tns__ they also share one document, and ?wsdl returns the whole contract — this is the common case and nothing else is needed.

A WSDL 1.1 definitions document carries exactly one targetNamespace, so services declaring different __tns__ values cannot be described by a single document. soapbar publishes one document per namespace instead, each with its own port types, bindings and SOAP version:

soap_app.register(BillingService())   # __tns__ = "http://example.com/billing"
soap_app.register(ShippingService())  # __tns__ = "http://example.com/shipping"

soap_app.wsdl_service_names          # ['Billing', 'Shipping']
soap_app.get_wsdl()                  # the first namespace's document
soap_app.get_wsdl("Shipping")        # the shipping document

Over HTTP the same selection is GET ?wsdl=Shipping; a name that matches no registered service answers 404. Bare ?wsdl keeps returning the first service's document, and that document's wsdl:documentation element names the alternatives so they are discoverable.

Services sharing a namespace but differing in binding style or SOAP version are published as separate port types and bindings inside the same document, which WSDL 1.1 allows — mixing SOAP 1.1 and 1.2 bindings simply declares both soap: and soap12: prefixes.

Parse an existing WSDL to inspect its structure:

from soapbar import parse_wsdl, parse_wsdl_file

defn = parse_wsdl(wsdl_bytes)          # from bytes/str
defn = parse_wsdl_file("service.wsdl") # from file

Custom WSDL override — supply your own WSDL document and skip auto-generation:

soap_app = SoapApplication(custom_wsdl=open("my_service.wsdl", "rb").read())

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:

# Default — safe for untrusted WSDLs; remote imports raise ValueError
defn = parse_wsdl(wsdl_bytes)

# Opt-in — only when the WSDL source is trusted
defn = parse_wsdl(wsdl_bytes, allow_remote_imports=True)

This 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.


WSDL schema validation

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.

from soapbar import SoapApplication

soap_app = SoapApplication(
    service_url="https://example.com/soap",
    validate_body_schema=True,   # X07 — WS-I BP 1.1 R2201
)
soap_app.register(MyService())

When enabled, the compiled lxml.etree.XMLSchema is built once — from any WSDL-embedded <xs:schema> elements plus the schema auto-generated from the registered services' types (exactly the schema the published WSDL advertises) — and cached, including a computed-unavailable result. Validation runs before deserialization, so a lexically invalid value (text in an xs:int element) surfaces as a Client fault with the first schema error message rather than a coercion error.

Strict mode enforces the published contract exactly: the generated schema declares elementFormDefault="unqualified" (matching soapbar's serializer), so a request that qualifies the wrapper's children — for example via a default xmlns on the wrapper — is rejected even though the lenient dispatcher would have accepted it. Only wrapped binding styles declare the global elements the validator needs; registering a non-wrapped service on an application with validate_body_schema=True raises ValueError instead of leaving the flag silently inert.