jambonz-python-sdk 0.2.0__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.
Files changed (120) hide show
  1. jambonz_python_sdk-0.2.0/.gitignore +16 -0
  2. jambonz_python_sdk-0.2.0/PKG-INFO +179 -0
  3. jambonz_python_sdk-0.2.0/README.md +145 -0
  4. jambonz_python_sdk-0.2.0/pyproject.toml +74 -0
  5. jambonz_python_sdk-0.2.0/src/jambonz_sdk/__init__.py +52 -0
  6. jambonz_python_sdk-0.2.0/src/jambonz_sdk/_signature.py +73 -0
  7. jambonz_python_sdk-0.2.0/src/jambonz_sdk/client/__init__.py +15 -0
  8. jambonz_python_sdk-0.2.0/src/jambonz_sdk/client/api.py +241 -0
  9. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/amd.schema.json +50 -0
  10. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/base.schema.json +29 -0
  11. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/call-status.schema.json +22 -0
  12. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/conference-status.schema.json +24 -0
  13. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/conference-wait.schema.json +11 -0
  14. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/conference.schema.json +11 -0
  15. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/dequeue.schema.json +19 -0
  16. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/dial-dtmf.schema.json +18 -0
  17. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/dial-hold.schema.json +22 -0
  18. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/dial-refer.schema.json +28 -0
  19. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/dial.schema.json +31 -0
  20. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/enqueue-wait.schema.json +17 -0
  21. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/enqueue.schema.json +27 -0
  22. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/gather-partial.schema.json +54 -0
  23. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/gather.schema.json +60 -0
  24. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/listen.schema.json +21 -0
  25. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/llm.schema.json +30 -0
  26. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/message.schema.json +35 -0
  27. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/pipeline-turn.schema.json +109 -0
  28. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/play.schema.json +36 -0
  29. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/session-new.schema.json +143 -0
  30. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/session-reconnect.schema.json +9 -0
  31. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/session-redirect.schema.json +38 -0
  32. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/sip-refer-event.schema.json +20 -0
  33. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/sip-refer.schema.json +22 -0
  34. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/sip-request.schema.json +27 -0
  35. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/transcribe-translation.schema.json +24 -0
  36. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/transcribe.schema.json +46 -0
  37. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/tts-streaming-event.schema.json +77 -0
  38. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/callbacks/verb-status.schema.json +57 -0
  39. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/actionHook.schema.json +36 -0
  40. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/actionHookDelayAction.schema.json +37 -0
  41. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/amd.schema.json +68 -0
  42. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/auth.schema.json +18 -0
  43. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/bidirectionalAudio.schema.json +22 -0
  44. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/fillerNoise.schema.json +25 -0
  45. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/llm-base.schema.json +94 -0
  46. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-assemblyAiOptions.schema.json +66 -0
  47. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-awsOptions.schema.json +52 -0
  48. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-azureOptions.schema.json +32 -0
  49. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-cobaltOptions.schema.json +34 -0
  50. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-customOptions.schema.json +27 -0
  51. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-deepgramOptions.schema.json +147 -0
  52. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-elevenlabsOptions.schema.json +39 -0
  53. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-gladiaOptions.schema.json +8 -0
  54. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-googleOptions.schema.json +35 -0
  55. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-houndifyOptions.schema.json +53 -0
  56. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-ibmOptions.schema.json +54 -0
  57. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-nuanceOptions.schema.json +150 -0
  58. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-nvidiaOptions.schema.json +39 -0
  59. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-openaiOptions.schema.json +59 -0
  60. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-sonioxOptions.schema.json +46 -0
  61. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-speechmaticsOptions.schema.json +100 -0
  62. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer-verbioOptions.schema.json +46 -0
  63. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/recognizer.schema.json +216 -0
  64. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/synthesizer.schema.json +82 -0
  65. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/target.schema.json +105 -0
  66. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/components/vad.schema.json +48 -0
  67. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/jambonz-app.schema.json +113 -0
  68. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/alert.schema.json +34 -0
  69. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/answer.schema.json +22 -0
  70. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/conference.schema.json +107 -0
  71. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/config.schema.json +221 -0
  72. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/deepgram_s2s.schema.json +81 -0
  73. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/dequeue.schema.json +51 -0
  74. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/dial.schema.json +200 -0
  75. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/dialogflow.schema.json +148 -0
  76. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/dtmf.schema.json +49 -0
  77. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/dub.schema.json +103 -0
  78. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/elevenlabs_s2s.schema.json +81 -0
  79. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/enqueue.schema.json +53 -0
  80. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/gather.schema.json +190 -0
  81. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/google_s2s.schema.json +42 -0
  82. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/hangup.schema.json +36 -0
  83. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/leave.schema.json +22 -0
  84. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/listen.schema.json +127 -0
  85. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/llm.schema.json +44 -0
  86. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/message.schema.json +82 -0
  87. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/openai_s2s.schema.json +42 -0
  88. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/pause.schema.json +36 -0
  89. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/pipeline.schema.json +240 -0
  90. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/play.schema.json +96 -0
  91. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/redirect.schema.json +34 -0
  92. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/rest_dial.schema.json +113 -0
  93. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/s2s.schema.json +39 -0
  94. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/say.schema.json +107 -0
  95. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/sip-decline.schema.json +58 -0
  96. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/sip-refer.schema.json +58 -0
  97. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/sip-request.schema.json +54 -0
  98. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/stream.schema.json +103 -0
  99. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/tag.schema.json +41 -0
  100. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/transcribe.schema.json +57 -0
  101. jambonz_python_sdk-0.2.0/src/jambonz_sdk/schema/verbs/ultravox_s2s.schema.json +41 -0
  102. jambonz_python_sdk-0.2.0/src/jambonz_sdk/types/__init__.py +139 -0
  103. jambonz_python_sdk-0.2.0/src/jambonz_sdk/types/components.py +250 -0
  104. jambonz_python_sdk-0.2.0/src/jambonz_sdk/types/rest.py +59 -0
  105. jambonz_python_sdk-0.2.0/src/jambonz_sdk/types/session.py +55 -0
  106. jambonz_python_sdk-0.2.0/src/jambonz_sdk/types/verbs.py +572 -0
  107. jambonz_python_sdk-0.2.0/src/jambonz_sdk/validator.py +107 -0
  108. jambonz_python_sdk-0.2.0/src/jambonz_sdk/verb_builder.py +316 -0
  109. jambonz_python_sdk-0.2.0/src/jambonz_sdk/verb_builder.pyi +1133 -0
  110. jambonz_python_sdk-0.2.0/src/jambonz_sdk/verb_registry.py +102 -0
  111. jambonz_python_sdk-0.2.0/src/jambonz_sdk/webhook/__init__.py +10 -0
  112. jambonz_python_sdk-0.2.0/src/jambonz_sdk/webhook/middleware.py +63 -0
  113. jambonz_python_sdk-0.2.0/src/jambonz_sdk/webhook/response.py +43 -0
  114. jambonz_python_sdk-0.2.0/src/jambonz_sdk/websocket/__init__.py +15 -0
  115. jambonz_python_sdk-0.2.0/src/jambonz_sdk/websocket/audio_client.py +11 -0
  116. jambonz_python_sdk-0.2.0/src/jambonz_sdk/websocket/audio_stream.py +151 -0
  117. jambonz_python_sdk-0.2.0/src/jambonz_sdk/websocket/client.py +165 -0
  118. jambonz_python_sdk-0.2.0/src/jambonz_sdk/websocket/endpoint.py +193 -0
  119. jambonz_python_sdk-0.2.0/src/jambonz_sdk/websocket/router.py +87 -0
  120. jambonz_python_sdk-0.2.0/src/jambonz_sdk/websocket/session.py +259 -0
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ .env
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ htmlcov/
13
+ .coverage
14
+ *.egg
15
+ mcp.json
16
+ .vscode/
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: jambonz-python-sdk
3
+ Version: 0.2.0
4
+ Summary: Python SDK for jambonz CPaaS platform
5
+ Project-URL: Homepage, https://github.com/jambonz/jambonz-python-sdk
6
+ Project-URL: Repository, https://github.com/jambonz/jambonz-python-sdk
7
+ Project-URL: Issues, https://github.com/jambonz/jambonz-python-sdk/issues
8
+ Project-URL: Documentation, https://jambonz.org
9
+ License-Expression: MIT
10
+ Keywords: cpaas,jambonz,sip,telephony,voip,websocket
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: AsyncIO
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Communications :: Telephony
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: aiohttp>=3.9
23
+ Requires-Dist: jsonschema>=4.20
24
+ Requires-Dist: referencing>=0.31
25
+ Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
26
+ Provides-Extra: dev
27
+ Requires-Dist: aioresponses>=0.7; extra == 'dev'
28
+ Requires-Dist: mypy>=1.10; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
30
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0; extra == 'dev'
32
+ Requires-Dist: ruff>=0.4; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # jambonz Python SDK
36
+
37
+ Python SDK for the [jambonz](https://jambonz.org) CPaaS platform.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install jambonz-python-sdk
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ### Webhook (HTTP)
48
+
49
+ ```python
50
+ from aiohttp import web
51
+ from jambonz_sdk.webhook import WebhookResponse
52
+
53
+ async def handle_incoming(request: web.Request) -> web.Response:
54
+ jambonz = WebhookResponse()
55
+ jambonz.say(text="Hello!").gather(
56
+ input=["speech"],
57
+ actionHook="/handle-input",
58
+ timeout=10,
59
+ say={"text": "Please say something."},
60
+ ).hangup()
61
+ return web.json_response(jambonz.to_json())
62
+
63
+ app = web.Application()
64
+ app.router.add_post("/incoming", handle_incoming)
65
+ web.run_app(app, port=3000)
66
+ ```
67
+
68
+ ### WebSocket
69
+
70
+ ```python
71
+ import asyncio
72
+ from jambonz_sdk.websocket import create_endpoint
73
+
74
+ async def main():
75
+ make_service, runner = await create_endpoint(port=3000)
76
+ svc = make_service(path="/")
77
+
78
+ async def handle_session(session):
79
+ session.say(text="Hello!").hangup()
80
+ await session.send()
81
+
82
+ svc.on("session:new", handle_session)
83
+ await asyncio.Future()
84
+
85
+ asyncio.run(main())
86
+ ```
87
+
88
+ ### REST Client
89
+
90
+ ```python
91
+ from jambonz_sdk.client import JambonzClient
92
+
93
+ async with JambonzClient(
94
+ base_url="https://api.jambonz.us",
95
+ account_sid="your-account-sid",
96
+ api_key="your-api-key",
97
+ ) as client:
98
+ call_sid = await client.calls.create({
99
+ "from": "+15085551212",
100
+ "to": {"type": "phone", "number": "+15085551213"},
101
+ "call_hook": "/incoming",
102
+ })
103
+ ```
104
+
105
+ ## How It Works
106
+
107
+ ### Spec-driven verb generation
108
+
109
+ The SDK does **not** hardcode verb method signatures. Instead, verb methods (`.say()`, `.gather()`, `.dial()`, `.pipeline()`, etc.) are **auto-generated at import time** from [JSON Schema](https://github.com/jambonz/schema) files — the same schemas used by the Node.js SDK and the jambonz server.
110
+
111
+ **What this means:**
112
+
113
+ - When the schema adds a new property to a verb, the SDK picks it up automatically — no code change needed
114
+ - Every method has **real typed parameters** (not `**kwargs: Any`) so IDEs show autocomplete and type hints
115
+ - Verb synonyms (`stream` ↔ `listen`, `openai_s2s` → `llm` with `vendor: "openai"`) are handled by the registry
116
+
117
+ ### Updating the schema
118
+
119
+ ```bash
120
+ # Download the pinned version from @jambonz/schema:
121
+ python scripts/sync_schema.py
122
+
123
+ # Or copy from a local clone:
124
+ python scripts/sync_schema.py --local /path/to/schema
125
+ ```
126
+
127
+ If a **new verb** was added (not just new properties), add one line to `verb_registry.py`:
128
+
129
+ ```python
130
+ VerbDef("new_verb", "new_verb", doc="Description.")
131
+ ```
132
+
133
+ ## Features
134
+
135
+ - **All 31 jambonz verbs**: say, play, gather, dial, conference, enqueue/dequeue, hangup, pause, redirect, config, tag, dtmf, dub, message, alert, answer, leave, listen/stream, transcribe, openai_s2s, google_s2s, deepgram_s2s, elevenlabs_s2s, ultravox_s2s, s2s, llm, dialogflow, pipeline, sip_decline, sip_request, sip_refer
136
+ - **Fluent chainable API**: `.say(...).gather(...).hangup()`
137
+ - **Webhook transport**: `WebhookResponse` for HTTP apps (works with aiohttp, FastAPI, Flask, etc.)
138
+ - **WebSocket transport**: `create_endpoint` with `Session`, event handling, `send()`/`reply()`
139
+ - **REST client**: `JambonzClient` with calls, conferences, queues, mid-call control
140
+ - **Audio streaming**: Bidirectional audio via `AudioStream`
141
+ - **Mid-call control**: inject commands (mute, whisper, record, DTMF, tag)
142
+ - **TTS token streaming**: `send_tts_tokens()` / `flush_tts_tokens()`
143
+ - **Pipeline updates**: `update_pipeline()` for mid-conversation LLM changes
144
+ - **Signature verification**: HMAC-SHA256 webhook signature validation
145
+ - **Env vars**: Portal discovery via OPTIONS + runtime reading
146
+
147
+ ## Examples
148
+
149
+ See the [`examples/`](examples/) directory:
150
+
151
+ | Example | Webhook | WebSocket | Description |
152
+ |---------|---------|-----------|-------------|
153
+ | hello-world | [webhook](examples/hello-world/webhook_app.py) | [websocket](examples/hello-world/websocket_app.py) | Minimal greeting |
154
+ | echo | [webhook](examples/echo/webhook_app.py) | [websocket](examples/echo/websocket_app.py) | Speech echo with gather |
155
+ | ivr-menu | [webhook](examples/ivr-menu/webhook_app.py) | — | IVR menu with speech + DTMF |
156
+ | voice-agent | [webhook](examples/voice-agent/webhook_app.py) | [websocket](examples/voice-agent/websocket_app.py) | LLM pipeline with tool calls |
157
+ | dial | [webhook](examples/dial/webhook_app.py) | — | Outbound dial with fallback |
158
+ | listen-record | [webhook](examples/listen-record/webhook_app.py) | [websocket](examples/listen-record/websocket_app.py) | Audio recording |
159
+
160
+ ## Development
161
+
162
+ ```bash
163
+ # Create venv and install
164
+ python3 -m venv .venv
165
+ source .venv/bin/activate
166
+ pip install -e ".[dev]"
167
+
168
+ # Run tests
169
+ pytest tests/unit/ # Fast unit tests (253)
170
+ pytest tests/integration/ # Real server tests (26)
171
+ pytest # All 279 tests
172
+
173
+ # Sync schema from upstream
174
+ python scripts/sync_schema.py
175
+ ```
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1,145 @@
1
+ # jambonz Python SDK
2
+
3
+ Python SDK for the [jambonz](https://jambonz.org) CPaaS platform.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install jambonz-python-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### Webhook (HTTP)
14
+
15
+ ```python
16
+ from aiohttp import web
17
+ from jambonz_sdk.webhook import WebhookResponse
18
+
19
+ async def handle_incoming(request: web.Request) -> web.Response:
20
+ jambonz = WebhookResponse()
21
+ jambonz.say(text="Hello!").gather(
22
+ input=["speech"],
23
+ actionHook="/handle-input",
24
+ timeout=10,
25
+ say={"text": "Please say something."},
26
+ ).hangup()
27
+ return web.json_response(jambonz.to_json())
28
+
29
+ app = web.Application()
30
+ app.router.add_post("/incoming", handle_incoming)
31
+ web.run_app(app, port=3000)
32
+ ```
33
+
34
+ ### WebSocket
35
+
36
+ ```python
37
+ import asyncio
38
+ from jambonz_sdk.websocket import create_endpoint
39
+
40
+ async def main():
41
+ make_service, runner = await create_endpoint(port=3000)
42
+ svc = make_service(path="/")
43
+
44
+ async def handle_session(session):
45
+ session.say(text="Hello!").hangup()
46
+ await session.send()
47
+
48
+ svc.on("session:new", handle_session)
49
+ await asyncio.Future()
50
+
51
+ asyncio.run(main())
52
+ ```
53
+
54
+ ### REST Client
55
+
56
+ ```python
57
+ from jambonz_sdk.client import JambonzClient
58
+
59
+ async with JambonzClient(
60
+ base_url="https://api.jambonz.us",
61
+ account_sid="your-account-sid",
62
+ api_key="your-api-key",
63
+ ) as client:
64
+ call_sid = await client.calls.create({
65
+ "from": "+15085551212",
66
+ "to": {"type": "phone", "number": "+15085551213"},
67
+ "call_hook": "/incoming",
68
+ })
69
+ ```
70
+
71
+ ## How It Works
72
+
73
+ ### Spec-driven verb generation
74
+
75
+ The SDK does **not** hardcode verb method signatures. Instead, verb methods (`.say()`, `.gather()`, `.dial()`, `.pipeline()`, etc.) are **auto-generated at import time** from [JSON Schema](https://github.com/jambonz/schema) files — the same schemas used by the Node.js SDK and the jambonz server.
76
+
77
+ **What this means:**
78
+
79
+ - When the schema adds a new property to a verb, the SDK picks it up automatically — no code change needed
80
+ - Every method has **real typed parameters** (not `**kwargs: Any`) so IDEs show autocomplete and type hints
81
+ - Verb synonyms (`stream` ↔ `listen`, `openai_s2s` → `llm` with `vendor: "openai"`) are handled by the registry
82
+
83
+ ### Updating the schema
84
+
85
+ ```bash
86
+ # Download the pinned version from @jambonz/schema:
87
+ python scripts/sync_schema.py
88
+
89
+ # Or copy from a local clone:
90
+ python scripts/sync_schema.py --local /path/to/schema
91
+ ```
92
+
93
+ If a **new verb** was added (not just new properties), add one line to `verb_registry.py`:
94
+
95
+ ```python
96
+ VerbDef("new_verb", "new_verb", doc="Description.")
97
+ ```
98
+
99
+ ## Features
100
+
101
+ - **All 31 jambonz verbs**: say, play, gather, dial, conference, enqueue/dequeue, hangup, pause, redirect, config, tag, dtmf, dub, message, alert, answer, leave, listen/stream, transcribe, openai_s2s, google_s2s, deepgram_s2s, elevenlabs_s2s, ultravox_s2s, s2s, llm, dialogflow, pipeline, sip_decline, sip_request, sip_refer
102
+ - **Fluent chainable API**: `.say(...).gather(...).hangup()`
103
+ - **Webhook transport**: `WebhookResponse` for HTTP apps (works with aiohttp, FastAPI, Flask, etc.)
104
+ - **WebSocket transport**: `create_endpoint` with `Session`, event handling, `send()`/`reply()`
105
+ - **REST client**: `JambonzClient` with calls, conferences, queues, mid-call control
106
+ - **Audio streaming**: Bidirectional audio via `AudioStream`
107
+ - **Mid-call control**: inject commands (mute, whisper, record, DTMF, tag)
108
+ - **TTS token streaming**: `send_tts_tokens()` / `flush_tts_tokens()`
109
+ - **Pipeline updates**: `update_pipeline()` for mid-conversation LLM changes
110
+ - **Signature verification**: HMAC-SHA256 webhook signature validation
111
+ - **Env vars**: Portal discovery via OPTIONS + runtime reading
112
+
113
+ ## Examples
114
+
115
+ See the [`examples/`](examples/) directory:
116
+
117
+ | Example | Webhook | WebSocket | Description |
118
+ |---------|---------|-----------|-------------|
119
+ | hello-world | [webhook](examples/hello-world/webhook_app.py) | [websocket](examples/hello-world/websocket_app.py) | Minimal greeting |
120
+ | echo | [webhook](examples/echo/webhook_app.py) | [websocket](examples/echo/websocket_app.py) | Speech echo with gather |
121
+ | ivr-menu | [webhook](examples/ivr-menu/webhook_app.py) | — | IVR menu with speech + DTMF |
122
+ | voice-agent | [webhook](examples/voice-agent/webhook_app.py) | [websocket](examples/voice-agent/websocket_app.py) | LLM pipeline with tool calls |
123
+ | dial | [webhook](examples/dial/webhook_app.py) | — | Outbound dial with fallback |
124
+ | listen-record | [webhook](examples/listen-record/webhook_app.py) | [websocket](examples/listen-record/websocket_app.py) | Audio recording |
125
+
126
+ ## Development
127
+
128
+ ```bash
129
+ # Create venv and install
130
+ python3 -m venv .venv
131
+ source .venv/bin/activate
132
+ pip install -e ".[dev]"
133
+
134
+ # Run tests
135
+ pytest tests/unit/ # Fast unit tests (253)
136
+ pytest tests/integration/ # Real server tests (26)
137
+ pytest # All 279 tests
138
+
139
+ # Sync schema from upstream
140
+ python scripts/sync_schema.py
141
+ ```
142
+
143
+ ## License
144
+
145
+ MIT
@@ -0,0 +1,74 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "jambonz-python-sdk"
7
+ version = "0.2.0"
8
+ description = "Python SDK for jambonz CPaaS platform"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ keywords = ["jambonz", "voip", "sip", "cpaas", "telephony", "websocket"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Communications :: Telephony",
23
+ "Framework :: AsyncIO",
24
+ ]
25
+ dependencies = [
26
+ "aiohttp>=3.9",
27
+ "jsonschema>=4.20",
28
+ "referencing>=0.31",
29
+ "typing_extensions>=4.0; python_version < '3.11'",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/jambonz/jambonz-python-sdk"
34
+ Repository = "https://github.com/jambonz/jambonz-python-sdk"
35
+ Issues = "https://github.com/jambonz/jambonz-python-sdk/issues"
36
+ Documentation = "https://jambonz.org"
37
+
38
+ [project.optional-dependencies]
39
+ dev = [
40
+ "pytest>=8.0",
41
+ "pytest-asyncio>=0.23",
42
+ "pytest-cov>=5.0",
43
+ "aioresponses>=0.7",
44
+ "ruff>=0.4",
45
+ "mypy>=1.10",
46
+ ]
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/jambonz_sdk"]
50
+
51
+ [tool.hatch.build]
52
+ include = ["src/jambonz_sdk/**/*.py", "src/jambonz_sdk/**/*.pyi", "src/jambonz_sdk/schema/**/*.json"]
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ asyncio_mode = "auto"
57
+ markers = [
58
+ "integration: integration tests that start real servers",
59
+ ]
60
+
61
+ [tool.ruff]
62
+ target-version = "py310"
63
+ line-length = 100
64
+
65
+ [tool.ruff.lint]
66
+ select = ["E", "F", "I", "W", "UP"]
67
+ ignore = ["UP007", "E501"] # UP007: Union needed at runtime; E501: long lines in test data
68
+
69
+ [tool.ruff.lint.per-file-ignores]
70
+ "*.pyi" = ["E501"]
71
+
72
+ [tool.mypy]
73
+ python_version = "3.10"
74
+ strict = true
@@ -0,0 +1,52 @@
1
+ """jambonz Python SDK.
2
+
3
+ A Python SDK for the jambonz CPaaS platform, providing:
4
+
5
+ - **Webhook**: HTTP transport for building jambonz voice apps
6
+ - **WebSocket**: Persistent connection transport for real-time voice AI
7
+ - **Client**: REST API client for call control and management
8
+
9
+ Quick start (webhook)::
10
+
11
+ from jambonz_sdk.webhook import WebhookResponse
12
+
13
+ jambonz = WebhookResponse()
14
+ jambonz.say(text="Hello!").hangup()
15
+ response_body = jambonz.to_json()
16
+
17
+ Quick start (websocket)::
18
+
19
+ from jambonz_sdk.websocket import create_endpoint
20
+
21
+ make_service, server = await create_endpoint(port=3000)
22
+ svc = make_service(path="/")
23
+
24
+ def handle_session(session):
25
+ session.say(text="Hello!").hangup()
26
+ await session.send()
27
+
28
+ svc.on("session:new", handle_session)
29
+
30
+ Quick start (REST client)::
31
+
32
+ from jambonz_sdk.client import JambonzClient
33
+
34
+ async with JambonzClient(base_url=url, account_sid=sid, api_key=key) as client:
35
+ call_sid = await client.calls.create({...})
36
+ """
37
+
38
+ __version__ = "0.1.0"
39
+
40
+ # Re-export main classes for convenience
41
+ from jambonz_sdk.client import JambonzClient
42
+ from jambonz_sdk.validator import JambonzValidator
43
+ from jambonz_sdk.verb_builder import VerbBuilder
44
+ from jambonz_sdk.webhook import WebhookResponse
45
+
46
+ __all__ = [
47
+ "JambonzClient",
48
+ "JambonzValidator",
49
+ "VerbBuilder",
50
+ "WebhookResponse",
51
+ "__version__",
52
+ ]
@@ -0,0 +1,73 @@
1
+ """HMAC-SHA256 webhook signature verification.
2
+
3
+ jambonz signs webhook requests with the header:
4
+ Jambonz-Signature: t=<timestamp>,v1=<signature>
5
+
6
+ The signature is computed as:
7
+ HMAC-SHA256(secret, timestamp + "." + raw_body)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ import hmac
14
+ import time
15
+
16
+ DEFAULT_TOLERANCE = 300 # 5 minutes
17
+
18
+
19
+ def verify_signature(
20
+ payload: bytes,
21
+ signature_header: str,
22
+ secret: str,
23
+ tolerance: int = DEFAULT_TOLERANCE,
24
+ ) -> bool:
25
+ """Verify a jambonz webhook signature.
26
+
27
+ Args:
28
+ payload: Raw request body bytes.
29
+ signature_header: Value of the ``Jambonz-Signature`` header.
30
+ secret: The webhook signing secret.
31
+ tolerance: Maximum age in seconds for the timestamp (default 300).
32
+
33
+ Returns:
34
+ True if the signature is valid.
35
+
36
+ Raises:
37
+ ValueError: If the signature header is malformed, the signature
38
+ doesn't match, or the timestamp is outside the tolerance window.
39
+ """
40
+ parts: dict[str, str] = {}
41
+ for item in signature_header.split(","):
42
+ key, _, value = item.strip().partition("=")
43
+ parts[key] = value
44
+
45
+ timestamp_str = parts.get("t")
46
+ sig = parts.get("v1")
47
+
48
+ if not timestamp_str or not sig:
49
+ raise ValueError("Invalid Jambonz-Signature header format")
50
+
51
+ try:
52
+ timestamp = int(timestamp_str)
53
+ except (ValueError, TypeError) as exc:
54
+ raise ValueError("Invalid timestamp in Jambonz-Signature header") from exc
55
+
56
+ if tolerance > 0:
57
+ age = int(time.time()) - timestamp
58
+ if age > tolerance:
59
+ raise ValueError(
60
+ f"Signature timestamp too old: {age}s > {tolerance}s tolerance"
61
+ )
62
+
63
+ signed_payload = f"{timestamp}.".encode() + payload
64
+ expected = hmac.new(
65
+ secret.encode(),
66
+ signed_payload,
67
+ hashlib.sha256,
68
+ ).hexdigest()
69
+
70
+ if not hmac.compare_digest(expected, sig):
71
+ raise ValueError("Signature verification failed")
72
+
73
+ return True
@@ -0,0 +1,15 @@
1
+ """REST API client for jambonz platform."""
2
+
3
+ from jambonz_sdk.client.api import (
4
+ CallsResource,
5
+ ConferencesResource,
6
+ JambonzClient,
7
+ QueuesResource,
8
+ )
9
+
10
+ __all__ = [
11
+ "CallsResource",
12
+ "ConferencesResource",
13
+ "JambonzClient",
14
+ "QueuesResource",
15
+ ]