Skip to main content

siphon-control (Python)

Python client for the SIPhon external control plane (siphon-control.v1) — an ARI/ESL-class rail for driving handed-over calls out of process. Built with PyO3 over the async Rust client, so the wire is hidden: no manual JSON, no request-id bookkeeping.

Two connection modes

The plane runs in one of two modes; both are exposed here and share the SAME @on_call decorator and the SAME Call handle — only the transport differs.

  • Inbound-persistent (ControlClient) — the app dials siphon and holds one long-lived socket (does the hello handshake). Simplest to reason about; use it for development and single-process controllers.
  • Per-call-connect (ControlServer) — siphon dials the app per handed-over call, so the app is a WebSocket server. Each accepted connection owns exactly one call and the first frame is a pushed StasisStart (no hello). This is the documented production default for multi-pod controllers: because the accepting socket is the call, "the audio lands on the wrong pod" can't happen.

Inbound-persistent

import asyncio
from siphon_control import ControlClient, ControlError

client = ControlClient(app="ivr-app", token="s3cr3t",
                       url="ws://siphon:9090/control/ws")

@client.on_call
async def handle(call):
    await call.answer()
    try:
        await call.transfer("sip:agent@pbx")   # REFER, awaits correlated reply
    except ControlError as error:
        print("transfer rejected:", error.code)

async def main():
    async with client:          # closes on the way out — see Shutdown below
        await client.run()

asyncio.run(main())

Per-call-connect

import asyncio
from siphon_control import ControlServer, ControlError

server = ControlServer(app="ivr-app", token="s3cr3t", bind="0.0.0.0:8790")

@server.on_call
async def handle(call):
    await call.answer()
    try:
        await call.transfer("sip:agent@pbx")
    except ControlError as error:
        print("transfer rejected:", error.code)

async def main():
    async with server:
        await server.serve()

asyncio.run(main())

Shutdown

Both classes are async context managers, and async with is the recommended shape. close() is the same thing explicitly.

It matters more than it looks. run() / serve() are driven by a background tokio task, and every handed-over call is dispatched from another one. Nothing joins those tasks and the runtime outlives the interpreter, so an app that finishes without closing leaves them delivering results into an asyncio loop — and then into a Python — that is no longer there. Closing first means there is nothing in flight to strand.

Not closing is handled rather than fatal: a handover arriving after the loop or interpreter has gone is dropped, and a handler cancelled during teardown is not reported as a failure. That is damage control, not a substitute for closing.

API

ControlClient (inbound-persistent)

  • ControlClient(app, token, url=…, protocol=1, reply_timeout_ms=…, reconnect_backoff_ms=…)
  • @client.on_call — register an async (or sync) per-call handler.
  • await client.connect() / await client.run() — connect / drive (reconnect + resync).
  • await client.command(verb, module=None, target=None, args=None) — the generic {module, verb, target, args} primitive for any adapter (SIP today; SMPP/SS7 later).
  • await client.originate(channel, to, *, media=False, sdp=None, body=None, content_type=None, …, session_timer=None) — place an outbound call under a channel id you choose; resolves to {"channel", "call_id", "sip_call_id"} once the INVITE is on the wire. Exactly one media plan (media=True, sdp= or body=). session_timer={"expires", "min_se", "refresher"} runs an RFC 4028 session timer on the call, each key left out taking the server's default. What the server would refuse raises ValueError before a frame goes out.
  • await client.describe() — adapter schema.
  • client.shutdown() — stop the client and unblock run().
  • client.close() — shutdown, plus drop the handler so nothing else is dispatched. async with client: does this on the way out. See Shutdown above.

ControlServer (per-call-connect)

  • ControlServer(app, token, bind="0.0.0.0:8790", reply_timeout_ms=…) — bind is the address the app listens on for siphon to dial; the token is validated on the incoming upgrade.
  • @server.on_call — the SAME decorator + Call handle as ControlClient.
  • await server.bind() — bind the listener; resolves to the bound address string (bind to …:0 to learn the ephemeral port before siphon dials in).
  • server.local_addr — the bound address once bind() / serve() has run, else None.
  • await server.serve() / await server.run() — accept siphon's per-call dials forever (stop by cancelling the task).
  • server.close() — drop the handler so no further accepted call is dispatched. async with server: does this on the way out. See Shutdown above.

Call (shared by both modes)

  • Call verbs: answer(), answer_with(code, …), progress(), reject(code, reason), hangup(reason=None), refer(to) / transfer(to), set_header(name, value), get_header(name), set_var(key, value), get_var(key), command(verb, args=None), next_event().
  • await call.dial(targets, strategy=None, timeout=None, headers=None) rings B-legs while the caller stays unanswered and this app keeps the channel. Each target is a dict: {"uri": ...} is dialed as written, {"aor": ...} is forked to every registered contact over that contact's own captured flow, which is the only way to reach a phone registered on TCP, TLS or WSS behind NAT. A bare string is refused, because it does not say which of the two was meant.
  • await call.record_start(direction=None, channels=None, max_duration_ms=None, silence_ms=None, path=None) records the call's decoded audio to a wav file and returns the recording_id that await call.record_stop(recording_id=None) addresses (no id stops every recording on the call). The reply is the accept; the RecordingFinished event says the file is closed. siphon-rtp backend only.
  • Media verbs play_file(file) / dtmf(digits) raise ControlError with code == "unsupported_verb" until the server implements media.

Errors

A rejected command raises ControlError carrying a stable .code (not_found, forbidden, unsupported_verb, unauthorized, …).

Build

maturin develop        # into the active venv
maturin build --release

The target interpreter is free-threaded CPython 3.14t (the SIPhon runtime); the wheel also loads on a standard GIL build.

License

MIT

Release files for siphon-control 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for siphon-control 0.5.0
File Size Uploaded
siphon_control-0.5.0.tar.gz 114.4 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for siphon-control 0.5.0
File
siphon_control-0.5.0-cp314-cp314t-win_amd64.whl CPython 3.14 CPython 3.14 free-threading Windows x86-64 Details
siphon_control-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ x86-64 Details
siphon_control-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ ARM64 Details
siphon_control-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl CPython 3.14 CPython 3.14 free-threading macOS 11.0+ ARM64 Details
siphon_control-0.5.0-cp314-cp314-win_amd64.whl CPython 3.14 CPython 3.14 Windows x86-64 Details
siphon_control-0.5.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.17+ x86-64 Details
siphon_control-0.5.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.14 CPython 3.14 Linux glibc 2.17+ ARM64 Details
siphon_control-0.5.0-cp314-cp314-macosx_11_0_arm64.whl CPython 3.14 CPython 3.14 macOS 11.0+ ARM64 Details

Total release size: 16.3 MB

Release files / siphon_control-0.5.0.tar.gz

Download URL siphon_control-0.5.0.tar.gz
Size 114.4 kB
Tags Source
SHA-256 checksum
How to use checksums
bdc27d93258643c74953b9b66a93c69a82b1bbede5a8f5da7f0a0bdf7e364570
BLAKE2b-256 checksum
How to use checksums
bfc1ae24898b18fa4509cbe1795b90cab619291870305473802fdf78d1c871b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / siphon_control-0.5.0-cp314-cp314t-win_amd64.whl

Download URL siphon_control-0.5.0-cp314-cp314t-win_amd64.whl
Size 1.8 MB
Tags CPython 3.14 CPython 3.14 free-threading Windows x86-64
SHA-256 checksum
How to use checksums
2602559d2b20fbf6250d6a16badcbec94ead812317a1ba8fc3a59ae44a08d31b
BLAKE2b-256 checksum
How to use checksums
d02fa452e832ee606c0405ee9794ab68bd634bf06846b3b2502e5af6e2e325a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / siphon_control-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL siphon_control-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 2.1 MB
Tags CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
f1a7b9976f0aec069bb6cabbd25e6cd9e4ebb3e051041d4eb72e93deda9bca31
BLAKE2b-256 checksum
How to use checksums
93f6dd65d6188200669b44da60a6b18b08cb2518c60ad8721020f4fbe6e21796
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / siphon_control-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL siphon_control-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 2.2 MB
Tags CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
c8363c3adc7d017aa248b8996c357d4986a218ddc0af7990a36655a22add5b54
BLAKE2b-256 checksum
How to use checksums
8e3f2671acc31e4608c6c05a36e77777f88c99f36107288e9d93ab90dea564d2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / siphon_control-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl

Download URL siphon_control-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl
Size 1.9 MB
Tags CPython 3.14 CPython 3.14 free-threading macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
6b73c863b3c83fdbf301dcd3c546e8aa4b3d2463ca604f9c2a4305b3155a4cd9
BLAKE2b-256 checksum
How to use checksums
b1670c664db86381bd3057f19e697d376e016c971ceb3a0bd812f94814abdf6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / siphon_control-0.5.0-cp314-cp314-win_amd64.whl

Download URL siphon_control-0.5.0-cp314-cp314-win_amd64.whl
Size 1.8 MB
Tags CPython 3.14 Windows x86-64
SHA-256 checksum
How to use checksums
6176005e732d054c446789eb4614e5de038dbd174eda29e0876ba49bad81a115
BLAKE2b-256 checksum
How to use checksums
a470db149d6e92263cce5aca56c1d19676e1eb173010c248606669d58f5d191b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / siphon_control-0.5.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL siphon_control-0.5.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 2.1 MB
Tags CPython 3.14 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
6725e843eb72b74cbbe49214060ccf7018ad30551450eafbef203baaf912af08
BLAKE2b-256 checksum
How to use checksums
02598f42b8174b07f4919f6b32a553539c934d4f063c5af76e23e24e6a571711
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / siphon_control-0.5.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL siphon_control-0.5.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 2.3 MB
Tags CPython 3.14 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
ff883ce1e32a4d12a90982a8c5e3697c0e77f5c08a53cc9be755bc8a2256871b
BLAKE2b-256 checksum
How to use checksums
51de9f31daa8a48189a9d04a94692f8aa542c9738667aad9e8fc146037e7cc6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / siphon_control-0.5.0-cp314-cp314-macosx_11_0_arm64.whl

Download URL siphon_control-0.5.0-cp314-cp314-macosx_11_0_arm64.whl
Size 1.9 MB
Tags CPython 3.14 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
fe0a4226679127b70b4e24537fda41a42667993c0ed8c48deb4510af9c1b5619
BLAKE2b-256 checksum
How to use checksums
aaa1d591fa8c38e695a17f0cfd856c9a2d4d0a3d84e6b853a17bdbc77326c67e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.0 This release

9 release files

0.4.0

9 release files

0.3.0

9 release files

0.2.0

9 release files

0.1.3

9 release files

0.1.2

9 release files

0.1.1

9 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page