Python’s xmlrpc.client module implements an XML-RPC client that calls remote methods over HTTP. import xmlrpc.client. ServerProxy: proxy = xmlrpc.client.ServerProxy("http://localhost:8000/RPC2") — all attribute accesses become remote calls; proxy.method(arg1, arg2) serializes args to XML, POSTs, and returns the decoded response. Type options: ServerProxy(url, allow_none=True) — marshals Python None as <nil/>; use_datetime=True — returns datetime.datetime instead of xmlrpc.client.DateTime; use_builtin_types=True — maps <base64> to bytes instead of xmlrpc.client.Binary. HTTPS: ServerProxy("https://host/RPC2", context=ssl.create_default_context()). MultiCall: mc = xmlrpc.client.MultiCall(proxy); mc.add(1,2); mc.ping(); results = mc() — batches all calls into one HTTP round-trip. Errors: xmlrpc.client.Fault — server raised an error (.faultCode, .faultString); xmlrpc.client.ProtocolError — HTTP error (.url, .errcode, .errmsg). Binary: xmlrpc.client.Binary(b"...") — wraps bytes for transmission. Introspection: proxy.system.listMethods(), proxy.system.methodHelp("name"), proxy.system.methodSignature("name"). Low-level: xmlrpc.client.dumps((arg,), "method") → XML string; xmlrpc.client.loads(xml) → (params, method). Claude Code generates WordPress API clients, legacy service bridges, batch RPC callers, and XML-RPC introspection tools.
CLAUDE.md for xmlrpc.client
## xmlrpc.client Stack
- Stdlib: import xmlrpc.client, ssl
- Call: proxy = xmlrpc.client.ServerProxy("http://host/RPC2", allow_none=True)
- Invoke: result = proxy.method_name(arg1, arg2)
- Batch: mc = xmlrpc.client.MultiCall(proxy); mc.fn1(); mc.fn2(); r1, r2 = mc()
- HTTPS: ServerProxy("https://host/RPC2", context=ssl.create_default_context())
- Errors: except xmlrpc.client.Fault as e: e.faultCode, e.faultString
xmlrpc.client RPC Pipeline
# app/xmlrpcutil.py — proxy, retry, batch, introspect, type helpers
from __future__ import annotations
import ssl
import time
import xmlrpc.client
from contextlib import contextmanager
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Callable, Generator, TypeVar
T = TypeVar("T")
# ─────────────────────────────────────────────────────────────────────────────
# 1. Proxy factory helpers
# ─────────────────────────────────────────────────────────────────────────────
def make_proxy(
url: str,
*,
allow_none: bool = True,
use_datetime: bool = True,
use_builtin_types: bool = True,
tls: bool | None = None,
verbose: bool = False,
) -> xmlrpc.client.ServerProxy:
"""
Create an XML-RPC ServerProxy with sensible modern defaults.
tls=True forces HTTPS context; None auto-detects from URL scheme.
Example:
proxy = make_proxy("http://localhost:8000/RPC2")
result = proxy.system.listMethods()
"""
kwargs: dict[str, Any] = {
"allow_none": allow_none,
"use_datetime": use_datetime,
"use_builtin_types": use_builtin_types,
"verbose": verbose,
}
use_tls = tls if tls is not None else url.startswith("https://")
if use_tls:
kwargs["context"] = ssl.create_default_context()
return xmlrpc.client.ServerProxy(url, **kwargs)
@contextmanager
def proxy_session(url: str, **kwargs) -> Generator[xmlrpc.client.ServerProxy, None, None]:
"""
Context manager that creates and closes an XML-RPC proxy.
Example:
with proxy_session("http://localhost:8000/RPC2") as proxy:
print(proxy.system.listMethods())
"""
proxy = make_proxy(url, **kwargs)
try:
yield proxy
finally:
proxy("close")() # type: ignore[operator]
# ─────────────────────────────────────────────────────────────────────────────
# 2. Error handling wrappers
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class RPCResult:
ok: bool
value: Any
fault_code: int = 0
fault_msg: str = ""
error: str = ""
def unwrap(self) -> Any:
"""Return value or raise appropriate exception."""
if not self.ok:
if self.fault_code:
raise xmlrpc.client.Fault(self.fault_code, self.fault_msg)
raise RuntimeError(self.error)
return self.value
def __str__(self) -> str:
if self.ok:
return f"OK: {self.value!r}"
if self.fault_code:
return f"Fault({self.fault_code}): {self.fault_msg}"
return f"Error: {self.error}"
def safe_call(fn: Callable[[], T]) -> RPCResult:
"""
Execute an XML-RPC call; return RPCResult instead of raising.
Example:
result = safe_call(lambda: proxy.do_something(42))
if result.ok:
print(result.value)
else:
print(result)
"""
try:
return RPCResult(ok=True, value=fn())
except xmlrpc.client.Fault as e:
return RPCResult(ok=False, value=None, fault_code=e.faultCode, fault_msg=e.faultString)
except xmlrpc.client.ProtocolError as e:
return RPCResult(ok=False, value=None, error=f"HTTP {e.errcode}: {e.errmsg}")
except Exception as e:
return RPCResult(ok=False, value=None, error=str(e))
def call_with_retry(
fn: Callable[[], T],
max_attempts: int = 3,
backoff: float = 1.0,
retry_faults: tuple[int, ...] = (),
) -> T:
"""
Call an XML-RPC method with retry on connection/transport errors.
retry_faults allows retrying specific XML-RPC fault codes.
Example:
result = call_with_retry(lambda: proxy.slow_method(), max_attempts=4)
"""
delay = backoff
last_exc: BaseException | None = None
for attempt in range(max_attempts):
try:
return fn()
except xmlrpc.client.Fault as e:
if e.faultCode in retry_faults and attempt < max_attempts - 1:
last_exc = e
else:
raise
except (xmlrpc.client.ProtocolError, OSError, TimeoutError) as e:
last_exc = e
if attempt == max_attempts - 1:
raise
time.sleep(delay)
delay *= 2.0
raise last_exc # type: ignore[misc]
# ─────────────────────────────────────────────────────────────────────────────
# 3. MultiCall helpers
# ─────────────────────────────────────────────────────────────────────────────
def batch_call(
proxy: xmlrpc.client.ServerProxy,
calls: list[tuple[str, tuple]],
) -> list[RPCResult]:
"""
Execute multiple RPC calls as a MultiCall batch (single HTTP round-trip).
calls: [(method_name, args_tuple), ...]
Example:
results = batch_call(proxy, [
("add", (1, 2)),
("multiply", (3, 4)),
("echo", ("hello",)),
])
for r in results:
print(r)
"""
mc = xmlrpc.client.MultiCall(proxy)
for method_name, args in calls:
getattr(mc, method_name)(*args)
results: list[RPCResult] = []
try:
for value in mc():
results.append(RPCResult(ok=True, value=value))
except xmlrpc.client.Fault as e:
results.append(RPCResult(ok=False, value=None,
fault_code=e.faultCode, fault_msg=e.faultString))
return results
# ─────────────────────────────────────────────────────────────────────────────
# 4. Introspection helpers
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class MethodInfo:
name: str
help_text: str = ""
signature: list[str] = field(default_factory=list)
def __str__(self) -> str:
sig = " | ".join(self.signature) if self.signature else "?"
return f"{self.name} sig={sig}\n {self.help_text[:80]}"
def introspect(proxy: xmlrpc.client.ServerProxy) -> list[MethodInfo]:
"""
List all methods exposed by the server (requires system.* support).
Example:
methods = introspect(proxy)
for m in methods:
print(m)
"""
try:
names: list[str] = proxy.system.listMethods()
except Exception:
return []
infos: list[MethodInfo] = []
for name in names:
help_text = ""
sig: list[str] = []
try:
help_text = proxy.system.methodHelp(name) or ""
except Exception:
pass
try:
raw_sig = proxy.system.methodSignature(name)
if isinstance(raw_sig, list):
sig = [" ".join(s) if isinstance(s, list) else str(s) for s in raw_sig]
except Exception:
pass
infos.append(MethodInfo(name=name, help_text=help_text, signature=sig))
return infos
# ─────────────────────────────────────────────────────────────────────────────
# 5. Serialization helpers
# ─────────────────────────────────────────────────────────────────────────────
def dumps_request(method: str, args: tuple) -> str:
"""
Serialize an XML-RPC request to XML without sending it.
Example:
xml = dumps_request("add", (1, 2))
print(xml)
"""
return xmlrpc.client.dumps(args, method, allow_none=True)
def loads_response(xml: str | bytes) -> tuple[Any, str | None]:
"""
Deserialize an XML-RPC response from XML bytes.
Returns (params, method_name_or_None).
Example:
params, _ = loads_response(b"<methodResponse>...</methodResponse>")
"""
if isinstance(xml, str):
xml = xml.encode()
return xmlrpc.client.loads(xml, use_datetime=True, use_builtin_types=True)
def wrap_bytes(data: bytes) -> xmlrpc.client.Binary:
"""Wrap bytes for XML-RPC Binary transport."""
return xmlrpc.client.Binary(data)
def unwrap_bytes(value: xmlrpc.client.Binary | bytes) -> bytes:
"""Unwrap XML-RPC Binary to Python bytes."""
if isinstance(value, xmlrpc.client.Binary):
return value.data
return value
# ─────────────────────────────────────────────────────────────────────────────
# Demo (spins up a minimal in-process XML-RPC server)
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import threading
from xmlrpc.server import SimpleXMLRPCServer
print("=== xmlrpc.client demo ===")
# ── Start a tiny in-process server ───────────────────────────────────────
PORT = 18700
def _serve() -> None:
server = SimpleXMLRPCServer(("127.0.0.1", PORT), logRequests=False)
server.register_introspection_functions()
server.register_function(lambda a, b: a + b, "add")
server.register_function(lambda a, b: a * b, "multiply")
server.register_function(lambda s: s.upper(), "shout")
server.register_function(lambda: None, "return_none")
server.handle_request() # serves 3 requests then exits
server.handle_request()
server.handle_request()
t = threading.Thread(target=_serve, daemon=True)
t.start()
import time; time.sleep(0.05)
proxy = make_proxy(f"http://127.0.0.1:{PORT}/")
# ── basic calls ───────────────────────────────────────────────────────────
print("\n--- basic calls ---")
print(f" add(3, 4) = {proxy.add(3, 4)}")
print(f" multiply(6, 7) = {proxy.multiply(6, 7)}")
print(f" shout('hello') = {proxy.shout('hello')!r}")
# ── safe_call ─────────────────────────────────────────────────────────────
print("\n--- safe_call ---")
r = safe_call(lambda: proxy.add(10, 20))
print(f" {r}")
# ── dumps_request ─────────────────────────────────────────────────────────
print("\n--- dumps_request ---")
xml = dumps_request("add", (1, 2))
print(f" {xml[:80].strip()!r}")
# ── introspect (requires the 3rd handle_request) ──────────────────────────
print("\n--- introspect ---")
try:
methods = introspect(proxy)
for m in methods[:5]:
print(f" {m.name}")
except Exception as e:
print(f" {e}")
t.join(timeout=2.0)
print("\n=== done ===")
For the xmlrpc.server alternative — xmlrpc.server.SimpleXMLRPCServer is the server-side companion exposing Python functions as RPC endpoints; it is used by the in-process demo above; the client and server together form a complete XML-RPC stack — they are complementary rather than alternative; xmlrpc.client is always the calling side and xmlrpc.server is the serving side. For the requests + JSON-RPC alternative — JSON-RPC over HTTP (typically via requests and a PyPI library like jsonrpcclient) provides semantically equivalent remote procedure calling with a lighter payload and native JSON types; it is the modern replacement for XML-RPC — use XML-RPC when integrating with existing systems that already speak it (WordPress XML-RPC API, Bugzilla, legacy Python services that use xmlrpc.server); use JSON-RPC for new inter-service communication where you control both sides. The Claude Skills 360 bundle includes xmlrpc.client skill sets covering make_proxy()/proxy_session() factory helpers, RPCResult dataclass with safe_call()/unwrap(), call_with_retry() with exponential backoff, batch_call() MultiCall helper, MethodInfo with introspect() for server discovery, and dumps_request()/loads_response()/wrap_bytes()/unwrap_bytes() serialization utilities. Start with the free tier to try XML-RPC client patterns and xmlrpc.client pipeline code generation.