zeep review
Zeep turns a SOAP WSDL into Python service methods and schema-aware values. It loads the contract and imported XSD files, maps operation arguments into XML, sends requests through Requests or HTTPX, and converts SOAP responses back into Python objects. SOAP 1.1 and 1.2, WS-Addressing, UsernameToken, X.509 signing, complex schema types, plugins, and raw-response access are covered. Version 4.3.3 finally activates `Settings(forbid_external=True)`, blocking transitive HTTP imports and entity references from an untrusted WSDL to reduce SSRF exposure. Our pure Python install was typed and imported in 0.42 seconds.
Zeep remains the practical Python client for a real WSDL, especially when WS-Security and imported schemas make hand-written XML risky. Use 4.3.3's external-reference block for untrusted contracts, and choose the vendor's REST API whenever it is equally supported.
We installed it
| Install | ✓ · 0.5s | 12 packages on disk · 16 MB |
| Import | ✓ | import zeep in 0.42s · pure Python · py.typed · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does zeep install cleanly?
Yes. In a fresh container with an empty cache, pip install zeep finished in 0.5s, leaving 12 packages and 16 MB on disk. pip-audit reported no known vulnerabilities.
What does zeep need to run?
Python >=3.10, and nothing compiled: it is pure Python. In our run import zeep succeeded in 0.42s, and the package ships py.typed for type checkers.
zeep or suds: which should you use?
suds: Use it only when a legacy codebase already depends heavily on Suds objects and a migration is out of scope. Zeep remains the practical Python client for a real WSDL, especially when WS-Security and imported schemas make hand-written XML risky.
When should you not use zeep?
The provider has a supported REST or JSON endpoint. A SOAP stack adds WSDL parsing, XML schema behavior, and more dependencies to operate
Use it if
- A bank, carrier, government system, or ERP exposes a WSDL and requires SOAP envelopes rather than JSON
- The contract uses imported schemas, nested complex types, SOAP 1.2, WS-Addressing, or WS-Security headers
- You need to inspect operation signatures with `python -m zeep` before writing integration code
- An existing Python SOAP integration needs a maintained replacement for older Suds-style clients
- The provider has a supported REST or JSON endpoint. A SOAP stack adds WSDL parsing, XML schema behavior, and more dependencies to operate
- You expect rapid feature development. The maintainer calls Zeep stable, says SOAP receives little new work, and GitHub reports 468 open issues and pull requests
- The service emits nonconforming XML and cannot be fixed. `strict=False` uses recovery parsing and the documentation warns that data may be lost
- Client construction must be fully asynchronous. `AsyncClient` still loads its WSDL synchronously before operations can use HTTPX
- The application accepts arbitrary WSDL URLs and cannot restrict outbound access. Version 4.3.3 can block transitive external imports, but the initial user-supplied URL is still fetched
Setup reality
We installed Zeep 4.3.3 in a fresh Python 3.12 Bookworm container. pip completed in 0.5 seconds, leaving 12 packages and 16 MB on disk. Package metadata lists 11 direct dependencies, requires Python 3.10 or newer, and describes a pure Python package with py.typed and an MIT license. import zeep completed in 0.42 seconds. pip-audit found no known vulnerabilities in the resolved environment.
Client() immediately fetches the entry WSDL and its imported schemas. The default transport does not cache those documents, and document loading uses a separate timeout from SOAP operations. Create the client once, add SqliteCache when contract caching is acceptable, set both timeout and operation_timeout, and reuse the proxy. The WSDL request shares the configured Requests session, so basic auth, client certificates, private CA bundles, and proxies must be ready before construction.
Install zeep[async] for HTTPX operation calls and zeep[xmlsec] for X.509 signing. XMLSec and lxml may require native libraries or compilation on Alpine and unusual architectures even though Zeep itself is pure Python. AsyncClient's operation methods await correctly, yet WSDL loading remains synchronous. Construct it during startup or move construction off the event loop rather than doing it inside an async request handler.
For WSDL content outside your control, use Settings(forbid_external=True) in 4.3.3 and restrict the initial URL with your own allowlist. That setting refuses transitive HTTP and HTTPS imports plus external entity resolution, while still loading the URL supplied to Client. Strict mode is safer for contract fidelity. If a vendor forces strict=False, capture raw XML in tests and verify every required field because recovery parsing can discard malformed content.
Patterns
Load a WSDL and call an operation create-client
from zeep import Client
client = Client('https://partner.example/service?wsdl')
quote = client.service.GetQuote(symbol='AAPL')Construction downloads the WSDL and imported schemas. Reuse the client rather than rebuilding it per request.
Print operation signatures inspect-wsdl
python -m zeep 'https://partner.example/service?wsdl'The command lists bindings, namespaces, types, and operation arguments before application code is written.
Authenticate the WSDL and SOAP calls configure-auth
from requests import Session
from requests.auth import HTTPBasicAuth
from zeep import Client
from zeep.transports import Transport
session = Session()
session.auth = HTTPBasicAuth(username, password)
client = Client(wsdl_url, transport=Transport(session=session))The same session loads the WSDL, so auth and CA settings must exist before client construction.
Bound requests and cache contracts set-timeouts-cache
from zeep.cache import SqliteCache
from zeep.transports import Transport
transport = Transport(
cache=SqliteCache(path='/var/cache/app/zeep.db', timeout=3600),
timeout=10,
operation_timeout=30,
)
client = Client(wsdl_url, transport=transport)`timeout` covers WSDL and schema loading; `operation_timeout` covers service calls. No cache is used by default.
Block transitive WSDL fetches forbid-external-imports
from zeep import Client, Settings
settings = Settings(forbid_external=True)
client = Client(allowed_wsdl_url, settings=settings)Version 4.3.3 blocks external imports and entities. Your code must still allowlist the initial URL passed to `Client`.
Send a WS-Security UsernameToken username-token
from zeep import Client
from zeep.wsse.username import UsernameToken
client = Client(
wsdl_url,
wsse=UsernameToken(username, password, use_digest=True),
)This credential lives in the SOAP header. Some providers also require separate HTTP authentication.
Create a schema-defined value construct-complex-type
factory = client.type_factory('ns0')
address = factory.Address(street='Main St 1', city='Springfield')
Order = client.get_type('ns0:Order')
order = Order(id=1, shipTo=address)
client.service.SubmitOrder(order=order)Get namespace prefixes from the Zeep CLI output. Factory objects catch schema-shape mistakes earlier than deeply nested dictionaries.
Record the latest SOAP exchange capture-soap-xml
from lxml import etree
from zeep.plugins import HistoryPlugin
history = HistoryPlugin(maxlen=1)
client = Client(wsdl_url, plugins=[history])
client.service.Ping()
xml = etree.tostring(history.last_sent['envelope'], pretty_print=True)Remove tokens, signatures, and business data before attaching captured envelopes to a support ticket.
Inspect an untouched HTTP response raw-response
with client.settings(raw_response=True):
response = client.service.GetReport(reportId=42)
response.raise_for_status()
xml_bytes = response.contentThe setting context applies only inside the block and returns a Requests response instead of a Zeep value.
Scope recovery parsing to one client lenient-parsing
from zeep import Client, Settings
client = Client(wsdl_url, settings=Settings(strict=False))
result = client.service.LegacyOperation()The documentation warns that recover mode may lose malformed data. Assert important response fields in integration tests.
Await SOAP operations async-operation
import zeep
client = zeep.AsyncClient(wsdl_url)
try:
result = await client.service.Ping()
finally:
await client.transport.aclose()Install `zeep[async]`. WSDL construction is still synchronous, so do it during startup or outside the event loop.
Convert Zeep values to plain containers serialize-response
from zeep import helpers
result = client.service.GetReport(reportId=42)
data = helpers.serialize_object(result, target_cls=dict)Dates and datetimes remain Python objects after conversion and need a JSON encoding policy.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| suds | PyPI | Use it only when a legacy codebase already depends heavily on Suds objects and a migration is out of scope. |
| requests | PyPI | Use it for one or two fixed operations when maintaining explicit XML templates is simpler than loading a WSDL model. |
| spyne | PyPI | Use it when Python must publish a SOAP service; Zeep is a client. |
More web backend guides
urllib3 · requests · ws · anyio · httpx · undici · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

