Python client for Cambium serve mode (RED-360). Speaks the v1 wire format over HTTP.
Project description
cambium-client
Python client for Cambium serve mode (RED-360). Speaks the v1 HTTP wire format — pip install cambium-client, point at a running cambium serve process, call gens.
The transport for non-Node hosts (FastAPI, Django, Flask, Go via HTTP, Elixir, anything that needs warm Cambium without a Node bridge). One httpx-backed CambiumClient exposes both sync and async paths from the same connection pool.
Install
pip install cambium-client
Requires Python 3.10+. Single runtime dependency: httpx>=0.27.2.
Quickstart
Run cambium serve somewhere reachable (loopback for the same-host case; Docker network for sidecar):
cambium serve --workspace ./cambium --bind tcp://127.0.0.1:9000
Then from Python:
from cambium_client import CambiumClient
# Sync
with CambiumClient(url="http://127.0.0.1:9000") as client:
output = client.run("ResumeParser", "analyze", resume_text)
# → whatever the gen's `returns` schema produced
# Async
async with CambiumClient(url="http://127.0.0.1:9000") as client:
output = await client.run_async("ResumeParser", "analyze", resume_text)
client.run() returns the bare output dict on success. Failures raise a typed CambiumError subclass — see Errors below.
Transports
| Scheme | v1 support | Notes |
|---|---|---|
http://host:port / https://... |
✅ | Standard httpx HTTP transport. |
tcp://host:port |
✅ | Convenience form (matches cambium serve --bind); rewritten to http://. |
unix:///abs/path |
✅ | UDS via httpx.HTTPTransport(uds=...). Mac / Linux. |
pipe://name |
❌ (v1.1) | Windows named pipes — raises NotImplementedError until v1.1. Use tcp://127.0.0.1:<port> on Windows in v1. |
API
CambiumClient(url, *, timeout=30.0, headers=None, probe=False)
| Argument | Type | Default | Notes |
|---|---|---|---|
url |
str |
(required) | Server URL. See Transports. |
timeout |
float |
30.0 |
Per-request timeout in seconds. |
headers |
Mapping[str, str] |
None |
Extra HTTP headers (e.g. tracing). |
probe |
bool |
False |
If True, call /v1/healthz from the constructor and raise CambiumConnectionError if the server isn't reachable (or BootingError if it's still loading gens). |
client.run(gen, method, input, *, memory_keys=None, fired_by=None, include_trace=False)
Dispatch a gen run. Returns the bare output from the response on success; raises a CambiumError subclass on failure.
| Argument | Type | Notes |
|---|---|---|
gen |
str |
Gen export name from the server's Genfile.toml [exports.gens]. |
method |
str |
Public method on the GenModel (typically analyze). |
input |
str | dict | list | bytes |
Bytes are decoded UTF-8 (JSON can't carry raw bytes). Dicts/lists pass through unchanged; the server stringifies non-string input values into ir.context.<source>. |
memory_keys |
Mapping[str, str] |
Values for keyed_by memory slots. Forwarded to the server as a dict. |
fired_by |
str |
Schedule id for cron-fired runs (e.g. "schedule:daily.x"). |
include_trace |
bool |
If True, the server returns the trace inline. The current run() API still returns just the output; trace surfacing in the response is a follow-up (run_envelope()). |
client.healthz()
Probe /v1/healthz. Returns a Healthz dataclass (status, gens, version). Raises BootingError while the server is still pre-compiling gens, CambiumConnectionError if it can't be reached.
Async parity
client.run_async(...) and client.healthz_async() mirror their sync siblings with identical signatures and exception trees. Use either path on the same CambiumClient instance — both pools live independently. async with cleans up both.
Context manager
# Sync
with CambiumClient(url=...) as client:
...
# Async
async with CambiumClient(url=...) as client:
...
Outside a context manager, call client.close() (sync) or await client.aclose() (async) explicitly.
Errors
Every server failure surfaces as a CambiumError subclass keyed off the wire error.kind enum. Catch by subclass for fine-grained handling, or CambiumError as the umbrella:
error.kind |
Exception | When |
|---|---|---|
unknown_gen |
UnknownGenError |
Gen name not in catalog. |
unknown_method |
UnknownMethodError |
Gen exists, method does not. |
input_invalid |
InputInvalidError |
Malformed body, missing required fields, oversize. |
validation_failed |
ValidationFailedError |
Schema validation exhausted after repair attempts. |
budget_exhausted |
BudgetExhaustedError |
BudgetExceededError inside runGen. |
tool_dispatch_failed |
ToolDispatchFailedError |
Unknown tool, action, or security violation. |
runner_error |
RunnerError (alias: CambiumRunError) |
Other runtime failures. |
timeout |
CambiumTimeoutError |
--run-timeout deadline missed. |
overloaded |
OverloadedError |
--max-inflight cap hit; backoff and retry. |
booting |
BootingError |
Server still pre-compiling at boot. |
not_found |
NotFoundError |
Unknown HTTP route. |
| (transport) | CambiumConnectionError (alias: CambiumNotFoundError) |
Server unreachable. |
Every exception carries .kind: str, .run_id: str | None (null on pre-dispatch errors), and .details: dict | None. Future server kinds we don't know about surface via the CambiumError base class with the real .kind populated rather than the request silently "succeeding."
from cambium_client import (
CambiumClient, BudgetExhaustedError, CambiumTimeoutError, OverloadedError,
)
with CambiumClient(url=...) as client:
try:
result = client.run("ResumeParser", "analyze", resume_text)
except OverloadedError:
# Back off + retry
...
except (BudgetExhaustedError, CambiumTimeoutError) as e:
# Surface to caller; e.run_id can be correlated with the on-disk trace
log.warning("cambium failure: kind=%s run_id=%s", e.kind, e.run_id)
raise
Subprocess fallback (recipe)
cambium-client is pure HTTP — it does NOT bake in a subprocess fallback for CAMBIUM_SERVE_URL unset. Callers who want graceful degradation when the server isn't available can wire it themselves in ~5 lines:
import os
from cambium_client import CambiumClient, CambiumConnectionError
def run_gen(gen: str, method: str, input_data):
url = os.environ.get("CAMBIUM_SERVE_URL")
if url:
with CambiumClient(url=url) as c:
return c.run(gen, method, input_data)
# Fallback: shell out to `cambium run` (your existing wrapper),
# or raise a clear "server not configured" error.
raise RuntimeError("CAMBIUM_SERVE_URL is unset and no fallback configured")
Migration from a subprocess wrapper
If you already have a services/cambium.py that wraps subprocess.run(["cambium", "run", ...]) with CambiumError / CambiumRunError / CambiumNotFoundError, this client keeps those names compatible. Replace the subprocess run() body with a CambiumClient(...).run() call; the exception names propagate.
Versioning
Wire-format pinning: cambium-client targets the server's /v1/ routes. The constant cambium_client.WIRE_VERSION is "v1". A future server /v2/ is a separate client release (cambium-client-v2-style or major bump).
Adheres to SemVer. The error.kind enum is closed in v1 — new kinds are a v2 break.
Publishing (maintainer)
Manual flow, mirroring the runner package's npm flow:
cd packages/cambium-client-python
# 1. Bump src/cambium_client/_version.py.
# 2. Build the wheel.
python -m build
# 3. Gate: pre-publish-check builds in a fresh venv + smoke-imports
# + asserts py.typed ships + asserts no stray tests/ in the wheel.
python scripts/pre_publish_check.py
# 4. Final check via twine.
python -m twine check dist/*
# 5. Smoke-test from TestPyPI first.
python -m twine upload --repository testpypi dist/*
# 6. Real PyPI.
python -m twine upload dist/*
# 7. Tag.
git tag cambium-client@v0.1.0
git push --tags
The pre_publish_check.py script is the analogue of the runner package's scripts/pre-publish-check.mjs: it validates the published artifact by building the wheel, installing it into a fresh venv (not the source tree), smoke-importing, and inspecting the wheel zip for stray test/cache files. Any failure exits non-zero.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cambium_client-0.1.0.tar.gz.
File metadata
- Download URL: cambium_client-0.1.0.tar.gz
- Upload date:
- Size: 32.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd2ab9410b05b38b9d0f2a75f51945757b902183dab7d331b3bcff72c0471731
|
|
| MD5 |
139a727895300072d5fcf5bdca0d566e
|
|
| BLAKE2b-256 |
9276dcbc0614901ba70c8c7838c6e0e9b0010c11412eb2ddfb04bffcdb85ef6f
|
File details
Details for the file cambium_client-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cambium_client-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
755f12b4a8b6092d9bf7dd62554c9c4f4f4cab36ab0a49810b60a5d71d49eade
|
|
| MD5 |
65a1d2be7b884201a13a1111db9d01a3
|
|
| BLAKE2b-256 |
5cc8aa7c843aa79031a9c2092c3887854750b954fd315f2b510b4e49d35b5cd7
|