Python で初めて SOAP API に接続する開発者が直面する最初の疑問は「どのライブラリを使うべきか?」です。Python のエコシステムには複数の選択肢があり、使いやすさ、WSDL サポート、メンテナンス状況でそれぞれ異なるトレードオフがあります。
このガイドでは zeep (現代的な選択)、requests + 手組み XML (最大限のコントロール)、suds-community (レガシー向け) の 3 つのアプローチを、完全なコード例、エラーハンドリングパターン、シナリオ別の推奨とともに解説します。
クイック比較表
| 機能 | zeep | requests + XML | suds-community |
|---|---|---|---|
| WSDL パース | 自動 | 手動 | 自動 |
| 型安全性 | あり (WSDL から) | なし | あり (WSDL から) |
| Python 3 対応 | 完全 | 完全 | コミュニティ fork |
| メンテナンス | アクティブ | N/A (標準ライブラリ) | 低頻度 |
| WS-Security | プラグインサポート | 手動 | 限定的 |
| 学習コスト | 低い | 高い | 低い |
| 複雑な型 | 自動処理 | 手動で XML 構築 | 自動処理 |
| 推奨シナリオ | ほとんどのプロジェクト | 単発の呼び出し | レガシーコードベース |
アプローチ 1: zeep (推奨)
zeep は Python で最も広く使われている SOAP ライブラリです。WSDL をパースし、すべての型に対応する Python オブジェクトを生成し、XML のシリアライズ・デシリアライズを自動で処理します。
インストール
pip install zeep
基本的な使い方
from zeep import Client
# WSDL URL からクライアントを初期化
client = Client("https://weather.example.com/soap?wsdl")
# オペレーションを呼び出し — zeep が XML エンベロープを構築
result = client.service.GetCurrentWeather(
city="Tokyo",
countryCode="JP"
)
# 結果は Python オブジェクト (XML ではない)
print(f"気温: {result.temperature}°{result.unit}")
print(f"天候: {result.condition}")
print(f"湿度: {result.humidity}%")
zeep は WSDL を読み込み、GetCurrentWeather オペレーションを発見し、正しい名前空間で適切な SOAP エンベロープを構築し、HTTP リクエストを送信し、XML レスポンスをパースして Python オブジェクトを返します。実質 3 行のコードでこのすべてが完了します。
複雑な型の取り扱い
SOAP オペレーションがネストされた構造や繰り返し要素を返す場合、zeep はそれらを Python オブジェクトにマッピングします:
client = Client("https://weather.example.com/soap?wsdl")
# GetForecast は ForecastDay オブジェクトのリストを返す
result = client.service.GetForecast(city="Tokyo", days=5)
for day in result.forecasts:
print(f"{day.date}: {day.low}°-{day.high}° ({day.condition})")
複雑な入力型を作成するには、zeep の型ファクトリを使います:
# 複雑な型のインスタンスを作成
factory = client.type_factory("ns0")
address = factory.Address(
street="丸の内1-1",
city="東京",
postalCode="100-0005",
country="JP"
)
result = client.service.CreateOrder(
customerId="CUST-001",
shippingAddress=address,
items=[
factory.OrderItem(productId="PROD-A", quantity=2),
factory.OrderItem(productId="PROD-B", quantity=1),
]
)
WS-Security 認証
from zeep import Client
from zeep.wsse.username import UsernameToken
# ユーザー名/パスワード認証
client = Client(
"https://secure.example.com/soap?wsdl",
wsse=UsernameToken("api_user", "api_password")
)
result = client.service.GetAccountBalance(accountId="ACC-12345")
タイムスタンプ付きトークンの場合:
from zeep.wsse.username import UsernameToken
from datetime import timedelta
client = Client(
"https://secure.example.com/soap?wsdl",
wsse=UsernameToken(
"api_user",
"api_password",
use_digest=True,
timestamp_token=True,
timestamp_freshness=timedelta(minutes=5)
)
)
エラーハンドリング
from zeep import Client
from zeep.exceptions import Fault, TransportError, ValidationError
from requests.exceptions import ConnectionError, Timeout
client = Client("https://weather.example.com/soap?wsdl")
try:
result = client.service.GetCurrentWeather(city="Tokyo")
except Fault as e:
# SOAP Fault — サーバーが構造化エラーを返した
print(f"SOAP Fault: {e.message}")
print(f"Fault code: {e.code}")
if e.detail is not None:
print(f"Detail: {e.detail}")
except TransportError as e:
# HTTP レベルのエラー (4xx, 5xx)
print(f"Transport error: {e.status_code} - {e.message}")
except ValidationError as e:
# WSDL スキーマに対する入力バリデーション失敗
print(f"Validation error: {e}")
except ConnectionError:
print("SOAP サービスに接続できませんでした")
except Timeout:
print("リクエストがタイムアウトしました")
トランスポート設定
from zeep import Client
from zeep.transports import Transport
from requests import Session
session = Session()
session.timeout = 30 # 秒
session.verify = "/path/to/ca-bundle.crt" # カスタム CA
session.headers.update({"User-Agent": "MyApp/1.0"})
# HTTP プロキシ
session.proxies = {
"https": "http://proxy.example.com:8080"
}
transport = Transport(session=session)
client = Client(
"https://weather.example.com/soap?wsdl",
transport=transport
)
アプローチ 2: requests + 手組み XML
SOAP 専用ライブラリをインストールせずに単一の SOAP オペレーションを呼び出したい場合、または WSDL が利用できないか壊れている場合に、Python の requests ライブラリで XML エンベロープを手動構築できます。
基本的な使い方
import requests
import xml.etree.ElementTree as ET
url = "https://weather.example.com/soap"
soap_envelope = """<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:tns="http://example.com/weatherservice">
<soap:Body>
<tns:GetCurrentWeather>
<tns:city>Tokyo</tns:city>
<tns:countryCode>JP</tns:countryCode>
</tns:GetCurrentWeather>
</soap:Body>
</soap:Envelope>"""
headers = {
"Content-Type": "text/xml; charset=utf-8",
"SOAPAction": "http://example.com/weatherservice/GetCurrentWeather",
}
response = requests.post(url, data=soap_envelope, headers=headers)
response.raise_for_status()
# XML レスポンスをパース
root = ET.fromstring(response.content)
# XPath クエリ用の名前空間定義
ns = {
"soap": "http://schemas.xmlsoap.org/soap/envelope/",
"tns": "http://example.com/weatherservice",
}
body = root.find(".//soap:Body", ns)
temp = body.find(".//tns:temperature", ns)
condition = body.find(".//tns:condition", ns)
print(f"気温: {temp.text}")
print(f"天候: {condition.text}")
パラメータ化されたリクエスト
XML インジェクションの脆弱性を避けるため、f-string や format() でユーザー入力を XML に直接挿入してはいけません。xml.etree.ElementTree でプログラム的にエンベロープを構築します:
import requests
import xml.etree.ElementTree as ET
def build_get_weather_envelope(city: str, country_code: str = None) -> str:
SOAP_NS = "http://schemas.xmlsoap.org/soap/envelope/"
TNS = "http://example.com/weatherservice"
ET.register_namespace("soap", SOAP_NS)
ET.register_namespace("tns", TNS)
envelope = ET.Element(f"{{{SOAP_NS}}}Envelope")
body = ET.SubElement(envelope, f"{{{SOAP_NS}}}Body")
operation = ET.SubElement(body, f"{{{TNS}}}GetCurrentWeather")
city_el = ET.SubElement(operation, f"{{{TNS}}}city")
city_el.text = city
if country_code:
cc_el = ET.SubElement(operation, f"{{{TNS}}}countryCode")
cc_el.text = country_code
return ET.tostring(envelope, encoding="unicode", xml_declaration=True)
envelope = build_get_weather_envelope("Tokyo", "JP")
response = requests.post(
"https://weather.example.com/soap",
data=envelope.encode("utf-8"),
headers={
"Content-Type": "text/xml; charset=utf-8",
"SOAPAction": "http://example.com/weatherservice/GetCurrentWeather",
},
timeout=30
)
SOAP Fault の検出
def check_soap_fault(response_content: bytes) -> None:
"""レスポンスに SOAP Fault が含まれている場合に例外を発生させる"""
root = ET.fromstring(response_content)
ns = {"soap": "http://schemas.xmlsoap.org/soap/envelope/"}
fault = root.find(".//soap:Fault", ns)
if fault is not None:
code = fault.findtext("faultcode", default="Unknown")
message = fault.findtext("faultstring", default="Unknown error")
detail = fault.find("detail")
detail_text = (
ET.tostring(detail, encoding="unicode")
if detail is not None else None
)
raise Exception(
f"SOAP Fault [{code}]: {message}"
+ (f"\nDetail: {detail_text}" if detail_text else "")
)
# 使用例
response = requests.post(url, data=envelope, headers=headers, timeout=30)
check_soap_fault(response.content)
アプローチ 3: suds-community
suds-community はオリジナルの suds ライブラリのコミュニティメンテナンス版 fork です。zeep と似た使い方ができますが、メンテナンス頻度は低めです。
インストール
pip install suds-community
基本的な使い方
from suds.client import Client
client = Client("https://weather.example.com/soap?wsdl")
# 利用可能なオペレーションを一覧表示
for service in client.wsdl.services:
for port in service.ports:
for method in port.methods.values():
print(f" {method.name}")
# オペレーションを呼び出し
result = client.service.GetCurrentWeather(city="Tokyo", countryCode="JP")
print(f"気温: {result.temperature}")
print(f"天候: {result.condition}")
suds を使うべき場面
suds-community が有用なのは主に以下のケースです:
- 既に suds を使っているコードベースのメンテナンス
- Python 2 からの移行コードとの互換性が必要
- zeep が処理できない WSDL の癖がある場合 (まれですが、存在します)
新規プロジェクトでは、アクティブなメンテナンス、優れた Python 3 サポート、充実した WS-Security 機能を持つ zeep が推奨されます。
パフォーマンス比較
天気予報サービスへの典型的な SOAP 呼び出しでの参考値:
| フェーズ | zeep | requests + XML | suds |
|---|---|---|---|
| WSDL パース (初回) | ~200-500ms | N/A | ~300-800ms |
| リクエスト構築 | ~1-5ms | ~0.1-0.5ms | ~2-8ms |
| ネットワーク往復 | 同等 | 同等 | 同等 |
| レスポンスパース | ~2-10ms | ~1-5ms | ~3-15ms |
requests によるアプローチが最も高速なのは、WSDL パースを完全にスキップするためです。ただし、zeep と suds はパース済み WSDL をキャッシュするため、初回のパースコストは後続の呼び出しで分散されます。
zeep では、アプリケーション再起動をまたいで WSDL パースをキャッシュできます:
from zeep import Client
from zeep.cache import SqliteCache
from zeep.transports import Transport
transport = Transport(
cache=SqliteCache(path="/tmp/zeep_cache.db", timeout=86400)
)
client = Client(
"https://weather.example.com/soap?wsdl",
transport=transport
)
ベストプラクティス
1. WSDL をキャッシュする
WSDL ファイルはめったに変更されません。一度パースしてクライアントオブジェクトを再利用しましょう:
# 良い例 — クライアントを一度だけ作成
client = Client("https://service.example.com/soap?wsdl")
def get_weather(city: str):
return client.service.GetCurrentWeather(city=city)
# 悪い例 — 毎回 WSDL をパース
def get_weather(city: str):
client = Client("https://service.example.com/soap?wsdl")
return client.service.GetCurrentWeather(city=city)
2. タイムアウトを設定する
SOAP サービスは遅いことがあります。常に明示的なタイムアウトを設定してください:
from zeep.transports import Transport
from requests import Session
session = Session()
session.timeout = 30
transport = Transport(session=session)
client = Client(wsdl_url, transport=transport)
3. デバッグ時に生の XML をログ出力する
問題が発生した際、実際の SOAP XML を確認できると非常に役立ちます:
import logging
logging.getLogger("zeep.transports").setLevel(logging.DEBUG)
4. SOAP Fault を個別にハンドリングする
汎用的な例外をキャッチしないでください。SOAP Fault は構造化されたエラー情報を持っています:
from zeep.exceptions import Fault
try:
result = client.service.ProcessPayment(amount=100)
except Fault as e:
if "InsufficientFunds" in str(e.detail):
handle_insufficient_funds()
elif e.code == "soap:Server":
retry_later()
else:
raise
判断フローチャート
適切なアプローチを選ぶためのフローチャートです:
複数の SOAP オペレーションを呼び出す必要がある?
├── はい → WSDL は利用可能で有効?
│ ├── はい → zeep を使う
│ └── いいえ → requests + XML (ドキュメントやサンプルを参照)
└── いいえ → 単発のスクリプト?
├── はい → requests + XML (依存が少ない)
└── いいえ → zeep を使う (長期的なメンテナンス性が高い)
SOAPless による解決
3 つのアプローチすべてで、Python コードが SOAP 固有の処理 — WSDL パース、XML エンベロープ構築、名前空間管理、SOAP Fault ハンドリング — を担う必要があります。SOAP サービスの数が増えるにつれて、この複雑さは倍増します。
SOAPless は Python コードから SOAP レイヤーを完全に排除します。zeep を使ったり XML を手組みしたりする代わりに、標準的な REST エンドポイントを呼び出すだけです:
import requests
response = requests.post(
"https://api.soapless.com/v1/your-service/GetCurrentWeather",
headers={
"X-API-Key": "your_api_key",
"Content-Type": "application/json",
},
json={"city": "Tokyo", "countryCode": "JP"},
timeout=30
)
data = response.json()
print(f"気温: {data['temperature']}°{data['unit']}")
zeep も不要、XML も不要、名前空間も不要、SOAP Fault のパースも不要です。SOAPless がサーバー側で WSDL パース、XML 変換、SOAP 通信を処理し、認証情報は AES-256-GCM で暗号化管理されます。自動生成される OpenAPI 3.0 仕様は Python 固有の SOAP ツールに限らず、あらゆる HTTP クライアントライブラリで利用できます。