soapix 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- soapix-0.1.1/.github/workflows/publish.yml +19 -0
- soapix-0.1.1/.github/workflows/test.yml +28 -0
- soapix-0.1.1/.gitignore +40 -0
- soapix-0.1.1/LICENSE +21 -0
- soapix-0.1.1/Makefile +93 -0
- soapix-0.1.1/PKG-INFO +417 -0
- soapix-0.1.1/README.md +385 -0
- soapix-0.1.1/pyproject.toml +61 -0
- soapix-0.1.1/soapix/__init__.py +14 -0
- soapix-0.1.1/soapix/__init__.pyi +6 -0
- soapix-0.1.1/soapix/cache.py +115 -0
- soapix-0.1.1/soapix/client.py +243 -0
- soapix-0.1.1/soapix/client.pyi +68 -0
- soapix-0.1.1/soapix/docs/__init__.py +0 -0
- soapix-0.1.1/soapix/docs/examples.py +91 -0
- soapix-0.1.1/soapix/docs/exporters.py +261 -0
- soapix-0.1.1/soapix/docs/generator.py +69 -0
- soapix-0.1.1/soapix/docs/resolver.py +60 -0
- soapix-0.1.1/soapix/docs/terminal.py +109 -0
- soapix-0.1.1/soapix/exceptions.py +113 -0
- soapix-0.1.1/soapix/py.typed +0 -0
- soapix-0.1.1/soapix/transport.py +183 -0
- soapix-0.1.1/soapix/wsdl/__init__.py +0 -0
- soapix-0.1.1/soapix/wsdl/namespace.py +59 -0
- soapix-0.1.1/soapix/wsdl/parser.py +553 -0
- soapix-0.1.1/soapix/wsdl/resolver.py +148 -0
- soapix-0.1.1/soapix/wsdl/types.py +118 -0
- soapix-0.1.1/soapix/xml/__init__.py +0 -0
- soapix-0.1.1/soapix/xml/builder.py +225 -0
- soapix-0.1.1/soapix/xml/parser.py +213 -0
- soapix-0.1.1/tests/__init__.py +0 -0
- soapix-0.1.1/tests/fixtures/any_type.wsdl +66 -0
- soapix-0.1.1/tests/fixtures/deep_import.wsdl +66 -0
- soapix-0.1.1/tests/fixtures/namespace_quirks.wsdl +64 -0
- soapix-0.1.1/tests/fixtures/simple.wsdl +107 -0
- soapix-0.1.1/tests/fixtures/soap12.wsdl +64 -0
- soapix-0.1.1/tests/fixtures/wrapped.wsdl +76 -0
- soapix-0.1.1/tests/test_cache.py +209 -0
- soapix-0.1.1/tests/test_docs.py +235 -0
- soapix-0.1.1/tests/test_exceptions.py +97 -0
- soapix-0.1.1/tests/test_namespace.py +73 -0
- soapix-0.1.1/tests/test_tolerant_engine.py +227 -0
- soapix-0.1.1/tests/test_wsdl_parser.py +112 -0
- soapix-0.1.1/tests/test_xml_builder.py +84 -0
- soapix-0.1.1/tests/test_xml_parser.py +101 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
steps:
|
|
2
|
+
- uses: actions/checkout@v4
|
|
3
|
+
|
|
4
|
+
- name: Set up Python
|
|
5
|
+
uses: actions/setup-python@v5
|
|
6
|
+
with:
|
|
7
|
+
python-version: "3.12"
|
|
8
|
+
|
|
9
|
+
- name: Install dependencies
|
|
10
|
+
run: pip install -e ".[dev]"
|
|
11
|
+
|
|
12
|
+
- name: Run tests
|
|
13
|
+
run: pytest
|
|
14
|
+
|
|
15
|
+
- name: Build package
|
|
16
|
+
run: pip install build && python -m build
|
|
17
|
+
|
|
18
|
+
- name: Publish to PyPI
|
|
19
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: pip install -e ".[dev]"
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: pytest tests/
|
soapix-0.1.1/.gitignore
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.so
|
|
7
|
+
|
|
8
|
+
# Distribution / packaging
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
*.egg
|
|
13
|
+
MANIFEST
|
|
14
|
+
|
|
15
|
+
# Virtual environments
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
env/
|
|
19
|
+
.env
|
|
20
|
+
|
|
21
|
+
# Testing & coverage
|
|
22
|
+
.coverage
|
|
23
|
+
.coverage.*
|
|
24
|
+
htmlcov/
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.tox/
|
|
27
|
+
|
|
28
|
+
# Type checking
|
|
29
|
+
.mypy_cache/
|
|
30
|
+
|
|
31
|
+
# Editor / IDE
|
|
32
|
+
.idea/
|
|
33
|
+
.vscode/
|
|
34
|
+
*.swp
|
|
35
|
+
*.swo
|
|
36
|
+
.DS_Store
|
|
37
|
+
|
|
38
|
+
# Generated docs output
|
|
39
|
+
api_docs.md
|
|
40
|
+
api_docs.html
|
soapix-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 soapix contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
soapix-0.1.1/Makefile
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# =====================================
|
|
2
|
+
# ⚙️ OPM - Odoo Plugin Manager (CLI)
|
|
3
|
+
# =====================================
|
|
4
|
+
|
|
5
|
+
# ----------------------
|
|
6
|
+
# 🔧 Configurable values
|
|
7
|
+
# ----------------------
|
|
8
|
+
|
|
9
|
+
ifneq (,$(wildcard .env))
|
|
10
|
+
include .env
|
|
11
|
+
export $(shell sed -n 's/^\([A-Za-z_][A-Za-z0-9_]*\)=.*/\1/p' .env)
|
|
12
|
+
endif
|
|
13
|
+
|
|
14
|
+
PYTHON := python3
|
|
15
|
+
PACKAGE := soapix
|
|
16
|
+
DIST_DIR := dist
|
|
17
|
+
|
|
18
|
+
# PyPI settings
|
|
19
|
+
PYPI_REPO := https://upload.pypi.org/legacy/
|
|
20
|
+
TEST_REPO := https://test.pypi.org/legacy/
|
|
21
|
+
|
|
22
|
+
# Version extraction from pyproject.toml
|
|
23
|
+
VERSION := $(shell sed -n 's/^version = "\([^"]*\)".*/\1/p' pyproject.toml | head -n1)
|
|
24
|
+
|
|
25
|
+
# ----------------------
|
|
26
|
+
# 🎯 Default target
|
|
27
|
+
# ----------------------
|
|
28
|
+
help:
|
|
29
|
+
@echo ""
|
|
30
|
+
@echo "🧩 OPM Build Commands:"
|
|
31
|
+
@echo " make build → Build wheel and sdist"
|
|
32
|
+
@echo " make clean → Remove temporary build files"
|
|
33
|
+
@echo " make version → Show current version"
|
|
34
|
+
@echo " make publish → Upload to PyPI"
|
|
35
|
+
@echo " make testpublish → Upload to TestPyPI"
|
|
36
|
+
@echo " make bump → Bump patch version (auto-increment)"
|
|
37
|
+
@echo " make release → Bump version, build, and publish to PyPI"
|
|
38
|
+
@echo ""
|
|
39
|
+
|
|
40
|
+
# ----------------------
|
|
41
|
+
# 🧹 Clean (remove build artifacts)
|
|
42
|
+
# ----------------------
|
|
43
|
+
clean:
|
|
44
|
+
@echo "🧹 Cleaning build artifacts..."
|
|
45
|
+
rm -rf $(DIST_DIR) build *.egg-info
|
|
46
|
+
@echo "✅ Clean complete."
|
|
47
|
+
|
|
48
|
+
# ----------------------
|
|
49
|
+
# 🔢 Bump version (auto-increment patch)
|
|
50
|
+
# ----------------------
|
|
51
|
+
bump:
|
|
52
|
+
@echo "🔢 Bumping patch version..."
|
|
53
|
+
@old=$$(sed -n 's/^[[:space:]]*version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' pyproject.toml | head -n1); \
|
|
54
|
+
if [ -z "$$old" ]; then echo "❌ Version not found in pyproject.toml"; exit 1; fi; \
|
|
55
|
+
new=$$(python3 -c "parts='$$old'.split('.'); parts[-1]=str(int(parts[-1])+1); print('.'.join(parts))"); \
|
|
56
|
+
if [ "$$(uname)" = "Darwin" ]; then \
|
|
57
|
+
sed -i '' -E "s/^([[:space:]]*version[[:space:]]*=[[:space:]]*)\"$$old\"/\1\"$$new\"/" pyproject.toml; \
|
|
58
|
+
else \
|
|
59
|
+
sed -i -E "s/^([[:space:]]*version[[:space:]]*=[[:space:]]*)\"$$old\"/\1\"$$new\"/" pyproject.toml; \
|
|
60
|
+
fi; \
|
|
61
|
+
echo "✅ New version: $$new"
|
|
62
|
+
|
|
63
|
+
# ----------------------
|
|
64
|
+
# 🏗️ Build (create wheel and sdist)
|
|
65
|
+
# ----------------------
|
|
66
|
+
build: clean
|
|
67
|
+
@echo "🏗️ Building $(PACKAGE) (version: $(VERSION))..."
|
|
68
|
+
PIP_CONFIG_FILE=/dev/null $(PYTHON) -m build
|
|
69
|
+
@echo "✅ Build complete. Artifacts in $(DIST_DIR)/"
|
|
70
|
+
|
|
71
|
+
# ----------------------
|
|
72
|
+
# 🚀 Publish (PyPI)
|
|
73
|
+
# ----------------------
|
|
74
|
+
publish: build
|
|
75
|
+
@echo "🚀 Publishing $(PACKAGE) $(VERSION) to PyPI..."
|
|
76
|
+
$(PYTHON) -m twine upload --repository-url $(PYPI_REPO) $(DIST_DIR)/*
|
|
77
|
+
@echo "✅ Published: https://pypi.org/project/$(PACKAGE)/$(VERSION)/"
|
|
78
|
+
|
|
79
|
+
# ----------------------
|
|
80
|
+
# 🧪 Test Publish (TestPyPI)
|
|
81
|
+
# ----------------------
|
|
82
|
+
testpublish:
|
|
83
|
+
@echo "🧪 Publishing $(PACKAGE) $(VERSION) to TestPyPI..."
|
|
84
|
+
$(PYTHON) -m twine upload --repository-url $(TEST_REPO) $(DIST_DIR)/*
|
|
85
|
+
@echo "✅ TestPyPI upload complete: https://test.pypi.org/project/$(PACKAGE)/$(VERSION)/"
|
|
86
|
+
|
|
87
|
+
# ----------------------
|
|
88
|
+
# 🔢 Version
|
|
89
|
+
# ----------------------
|
|
90
|
+
version:
|
|
91
|
+
@echo "📦 Current version: $(VERSION)"
|
|
92
|
+
|
|
93
|
+
release: bump publish
|
soapix-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: soapix
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A tolerant, self-documenting Python SOAP client library
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: client,soap,webservice,wsdl,xml
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: httpx>=0.27.0
|
|
19
|
+
Requires-Dist: lxml>=5.0.0
|
|
20
|
+
Requires-Dist: pydantic>=2.0.0
|
|
21
|
+
Requires-Dist: rich>=13.0.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build>=1.0.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# soapix
|
|
34
|
+
|
|
35
|
+
A tolerant, self-documenting Python SOAP client library.
|
|
36
|
+
|
|
37
|
+
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.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- **Tolerant validation** — optional fields can be omitted; required `None` fields send `xsi:nil` instead of crashing
|
|
42
|
+
- **Namespace tolerance** — trailing slashes, case differences, and URI fragments are normalized automatically
|
|
43
|
+
- **Auto-documentation** — generates terminal, Markdown, and HTML docs directly from the WSDL
|
|
44
|
+
- **Meaningful errors** — exceptions include service name, method, endpoint, sent payload, and a human-readable hint
|
|
45
|
+
- **Async support** — native `AsyncSoapClient` with `async/await`
|
|
46
|
+
- **WSDL caching** — in-memory and file-based caching with TTL
|
|
47
|
+
- **Retry & timeout** — configurable per-client
|
|
48
|
+
- **Type stubs** — full `.pyi` stubs for IDE autocomplete
|
|
49
|
+
|
|
50
|
+
## Requirements
|
|
51
|
+
|
|
52
|
+
- Python 3.10+
|
|
53
|
+
- Dependencies: `httpx`, `lxml`, `rich`, `pydantic`
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install soapix
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from soapix import SoapClient
|
|
65
|
+
|
|
66
|
+
client = SoapClient("http://service.example.com/?wsdl")
|
|
67
|
+
result = client.service.GetUser(userId=123)
|
|
68
|
+
print(result["name"])
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
All options are keyword-only and passed to the constructor.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
client = SoapClient(
|
|
79
|
+
"http://service.example.com/?wsdl",
|
|
80
|
+
timeout=60.0, # HTTP timeout in seconds (default: 30.0)
|
|
81
|
+
retries=3, # Retry count on transient failures (default: 0)
|
|
82
|
+
strict=False, # Strict WSDL validation (default: False)
|
|
83
|
+
debug=True, # Print request/response XML to terminal (default: False)
|
|
84
|
+
cache=None, # Cache instance, or None to disable (default: MemoryCache)
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Option | Type | Default | Description |
|
|
89
|
+
|--------|------|---------|-------------|
|
|
90
|
+
| `timeout` | `float` | `30.0` | HTTP request timeout in seconds |
|
|
91
|
+
| `retries` | `int` | `0` | Number of retries on connection/timeout errors |
|
|
92
|
+
| `strict` | `bool` | `False` | If `True`, raises on missing required fields instead of sending `xsi:nil` |
|
|
93
|
+
| `debug` | `bool` | `False` | Prints colourised request and response XML to the terminal |
|
|
94
|
+
| `cache` | `Cache \| None` | `MemoryCache` | WSDL parse cache; `None` disables caching |
|
|
95
|
+
| `verify` | `bool \| str` | `True` | SSL verification: `True` (system certs), `False` (skip), or path to a CA bundle file |
|
|
96
|
+
| `auth` | `tuple \| None` | `None` | HTTP Basic Auth credentials as `(username, password)` |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Calling Operations
|
|
101
|
+
|
|
102
|
+
Operations are accessed through `client.service.<OperationName>(...)` using keyword arguments that match the WSDL parameter names.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
# Simple call
|
|
106
|
+
result = client.service.GetUser(userId=42)
|
|
107
|
+
|
|
108
|
+
# Multiple parameters
|
|
109
|
+
result = client.service.CreateUser(name="Alice", email="alice@example.com")
|
|
110
|
+
|
|
111
|
+
# Optional parameters can be omitted in tolerant mode (default)
|
|
112
|
+
result = client.service.GetUser(userId=42) # locale is optional — omitted
|
|
113
|
+
result = client.service.GetUser(userId=42, locale="en-US") # or explicitly passed
|
|
114
|
+
|
|
115
|
+
# Required field sent as None → xsi:nil in tolerant mode
|
|
116
|
+
result = client.service.GetUser(userId=None)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The return value is a plain Python `dict` (or scalar for leaf values). Nested elements become nested dicts; repeated elements become lists.
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
result = client.service.GetUser(userId=1)
|
|
123
|
+
# {"userId": 1, "name": "Alice", "email": "alice@example.com", "active": True}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Async Client
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
import asyncio
|
|
132
|
+
from soapix import AsyncSoapClient
|
|
133
|
+
|
|
134
|
+
async def main():
|
|
135
|
+
async with AsyncSoapClient("http://service.example.com/?wsdl") as client:
|
|
136
|
+
result = await client.service.GetUser(userId=123)
|
|
137
|
+
print(result["name"])
|
|
138
|
+
|
|
139
|
+
asyncio.run(main())
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`AsyncSoapClient` accepts the same options as `SoapClient`. Use it as an async context manager to ensure the underlying HTTP connection is properly closed.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Strict Mode
|
|
147
|
+
|
|
148
|
+
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:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
client = SoapClient("http://service.example.com/?wsdl", strict=True)
|
|
152
|
+
|
|
153
|
+
# Raises SerializationError if a required field is missing
|
|
154
|
+
client.service.GetUser() # userId is required → raises
|
|
155
|
+
client.service.GetUser(userId=None) # None on required field → raises
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## SSL & Authentication
|
|
161
|
+
|
|
162
|
+
### SSL verification
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# Default — uses system certificate store
|
|
166
|
+
client = SoapClient("https://service.example.com/?wsdl")
|
|
167
|
+
|
|
168
|
+
# Custom CA bundle (corporate / self-signed certificates)
|
|
169
|
+
client = SoapClient("https://service.example.com/?wsdl", verify="/path/to/ca-bundle.pem")
|
|
170
|
+
|
|
171
|
+
# Disable SSL verification — development only, not recommended for production
|
|
172
|
+
client = SoapClient("https://service.example.com/?wsdl", verify=False)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
To obtain the server's CA certificate:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
openssl s_client -connect service.example.com:443 -showcerts 2>/dev/null \
|
|
179
|
+
| sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > ca.pem
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### HTTP Basic Auth
|
|
183
|
+
|
|
184
|
+
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:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
client = SoapClient(
|
|
188
|
+
"https://service.example.com/?wsdl",
|
|
189
|
+
auth=("username", "password"),
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Combined
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
client = SoapClient(
|
|
197
|
+
"https://service.example.com/?wsdl",
|
|
198
|
+
verify="/path/to/ca-bundle.pem",
|
|
199
|
+
auth=("username", "password"),
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Debug Mode
|
|
206
|
+
|
|
207
|
+
Enable `debug=True` to print the full SOAP envelope sent and the raw XML response to the terminal, with syntax highlighting via `rich`.
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
client = SoapClient("http://service.example.com/?wsdl", debug=True)
|
|
211
|
+
client.service.GetUser(userId=1)
|
|
212
|
+
# ── REQUEST ──────────────────────────────
|
|
213
|
+
# POST http://service.example.com/
|
|
214
|
+
# SOAPAction: "..."
|
|
215
|
+
#
|
|
216
|
+
# <?xml version="1.0" ...>
|
|
217
|
+
# <soap:Envelope ...>
|
|
218
|
+
# ...
|
|
219
|
+
# </soap:Envelope>
|
|
220
|
+
#
|
|
221
|
+
# ── RESPONSE (200 OK, 42ms) ──────────────
|
|
222
|
+
# <?xml version="1.0" ...>
|
|
223
|
+
# ...
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## WSDL Caching
|
|
229
|
+
|
|
230
|
+
soapix caches parsed WSDL documents to avoid re-fetching and re-parsing on every instantiation.
|
|
231
|
+
|
|
232
|
+
### MemoryCache (default)
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
from soapix.cache import MemoryCache
|
|
236
|
+
|
|
237
|
+
cache = MemoryCache(
|
|
238
|
+
ttl=300, # Seconds until entries expire (default: 300, None = no expiry)
|
|
239
|
+
maxsize=64, # Max entries before oldest is evicted (default: 64)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
client = SoapClient("http://service.example.com/?wsdl", cache=cache)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
A module-level default cache is shared across all `SoapClient` instances that don't specify one. Use `get_default_cache()` to access it:
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from soapix.cache import get_default_cache
|
|
249
|
+
get_default_cache().clear() # flush all cached WSDLs
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### FileCache
|
|
253
|
+
|
|
254
|
+
`FileCache` persists parsed WSDL documents to disk using `pickle`. Useful across process restarts.
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from soapix.cache import FileCache
|
|
258
|
+
|
|
259
|
+
cache = FileCache(
|
|
260
|
+
cache_dir=".soapix_cache", # Directory to store cache files (created if absent)
|
|
261
|
+
ttl=3600, # Seconds until entries expire (default: 3600)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
client = SoapClient("http://service.example.com/?wsdl", cache=cache)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Disable caching
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
client = SoapClient("http://service.example.com/?wsdl", cache=None)
|
|
271
|
+
# WSDL is fetched and parsed on every instantiation
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Auto-Documentation
|
|
277
|
+
|
|
278
|
+
soapix can generate human-readable documentation from the WSDL — terminal output, Markdown, or HTML.
|
|
279
|
+
|
|
280
|
+
### Terminal
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
client.docs()
|
|
284
|
+
# Prints a formatted table with all operations, parameters, types, and example calls.
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Markdown
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
# Returns a string
|
|
291
|
+
md = client.docs(output="markdown")
|
|
292
|
+
|
|
293
|
+
# Writes to a file
|
|
294
|
+
client.docs(output="markdown", path="api_docs.md")
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### HTML
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
# Returns a string
|
|
301
|
+
html = client.docs(output="html")
|
|
302
|
+
|
|
303
|
+
# Writes to a file (includes a search box)
|
|
304
|
+
client.docs(output="html", path="api_docs.html")
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
You can also use `DocsGenerator` directly if you have a parsed WSDL document:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
from soapix.docs.generator import DocsGenerator
|
|
311
|
+
from soapix.wsdl.parser import WsdlParser
|
|
312
|
+
|
|
313
|
+
doc = WsdlParser().load("service.wsdl")
|
|
314
|
+
gen = DocsGenerator(doc)
|
|
315
|
+
gen.render(output="terminal")
|
|
316
|
+
gen.render(output="markdown", path="api_docs.md")
|
|
317
|
+
gen.render(output="html", path="api_docs.html")
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Error Handling
|
|
323
|
+
|
|
324
|
+
soapix raises structured exceptions with actionable context.
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
from soapix.exceptions import (
|
|
328
|
+
SoapFaultError, # Server returned a soap:Fault
|
|
329
|
+
HttpError, # HTTP 4xx/5xx or connection failure
|
|
330
|
+
TimeoutError, # Request exceeded the timeout
|
|
331
|
+
SerializationError, # Python value could not be serialised to XML
|
|
332
|
+
WsdlParseError, # WSDL could not be read or parsed
|
|
333
|
+
WsdlNotFoundError, # WSDL URL or path not reachable
|
|
334
|
+
WsdlImportError, # xs:import could not be resolved
|
|
335
|
+
)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Exception hierarchy
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
SoapixError
|
|
342
|
+
├── WsdlParseError
|
|
343
|
+
│ ├── WsdlNotFoundError
|
|
344
|
+
│ └── WsdlImportError
|
|
345
|
+
├── SoapCallError
|
|
346
|
+
│ ├── SoapFaultError
|
|
347
|
+
│ ├── HttpError
|
|
348
|
+
│ └── TimeoutError
|
|
349
|
+
└── SerializationError
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Catching errors
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
from soapix.exceptions import SoapFaultError, HttpError, TimeoutError
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
result = client.service.GetUser(userId=999)
|
|
359
|
+
except SoapFaultError as e:
|
|
360
|
+
print(e.fault_code) # e.g. "Server"
|
|
361
|
+
print(e.fault_string) # e.g. "User not found"
|
|
362
|
+
print(e.detail) # raw XML detail block, if any
|
|
363
|
+
except TimeoutError:
|
|
364
|
+
print("Request timed out — increase timeout or check the endpoint")
|
|
365
|
+
except HttpError as e:
|
|
366
|
+
print(f"HTTP error: {e}")
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
Error messages include structured context:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
'GetUser' call failed
|
|
374
|
+
|
|
375
|
+
Service : UserService
|
|
376
|
+
Method : GetUser
|
|
377
|
+
Endpoint : http://service.example.com/
|
|
378
|
+
Sent : {'userId': None}
|
|
379
|
+
|
|
380
|
+
Hint : userId is required (int) — None cannot be sent in strict mode
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Retry & Timeout
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
client = SoapClient(
|
|
389
|
+
"http://service.example.com/?wsdl",
|
|
390
|
+
timeout=10.0, # fail fast
|
|
391
|
+
retries=3, # retry up to 3 times on connection/timeout errors
|
|
392
|
+
)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Retries apply to `HttpError` (connection failures) and `TimeoutError`. Server-side SOAP faults (`SoapFaultError`) are not retried.
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Comparison
|
|
400
|
+
|
|
401
|
+
| Feature | Zeep | Suds | soapix |
|
|
402
|
+
|---------|------|------|--------|
|
|
403
|
+
| Tolerant validation | No | No | Yes |
|
|
404
|
+
| Namespace tolerance | Partial | Partial | Full |
|
|
405
|
+
| Meaningful errors | No | No | Yes |
|
|
406
|
+
| Auto documentation | No | No | Yes |
|
|
407
|
+
| Async support | Partial | No | Native |
|
|
408
|
+
| WSDL caching | No | No | Yes |
|
|
409
|
+
| Retry & timeout | Manual | Manual | Built-in |
|
|
410
|
+
| Type stubs | Partial | No | Yes |
|
|
411
|
+
| Python 3.10+ | Yes | No | Yes |
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## License
|
|
416
|
+
|
|
417
|
+
MIT — see [LICENSE](LICENSE).
|