voiceml 0.7.1__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.
- voiceml-0.7.1/.gitignore +25 -0
- voiceml-0.7.1/LICENSE +23 -0
- voiceml-0.7.1/PKG-INFO +307 -0
- voiceml-0.7.1/README.md +274 -0
- voiceml-0.7.1/pyproject.toml +71 -0
- voiceml-0.7.1/src/voiceml/__init__.py +62 -0
- voiceml-0.7.1/src/voiceml/_http.py +316 -0
- voiceml-0.7.1/src/voiceml/_version.py +1 -0
- voiceml-0.7.1/src/voiceml/client.py +176 -0
- voiceml-0.7.1/src/voiceml/exceptions.py +121 -0
- voiceml-0.7.1/src/voiceml/models/__init__.py +195 -0
- voiceml-0.7.1/src/voiceml/models/_base.py +54 -0
- voiceml-0.7.1/src/voiceml/models/applications.py +56 -0
- voiceml-0.7.1/src/voiceml/models/calls.py +151 -0
- voiceml-0.7.1/src/voiceml/models/common.py +58 -0
- voiceml-0.7.1/src/voiceml/models/conferences.py +97 -0
- voiceml-0.7.1/src/voiceml/models/diagnostics.py +45 -0
- voiceml-0.7.1/src/voiceml/models/incoming_phone_numbers.py +120 -0
- voiceml-0.7.1/src/voiceml/models/messages.py +104 -0
- voiceml-0.7.1/src/voiceml/models/payments.py +97 -0
- voiceml-0.7.1/src/voiceml/models/queues.py +61 -0
- voiceml-0.7.1/src/voiceml/models/recordings.py +103 -0
- voiceml-0.7.1/src/voiceml/models/siprec.py +50 -0
- voiceml-0.7.1/src/voiceml/models/streams.py +43 -0
- voiceml-0.7.1/src/voiceml/models/transcriptions.py +55 -0
- voiceml-0.7.1/src/voiceml/py.typed +0 -0
- voiceml-0.7.1/src/voiceml/resources/__init__.py +37 -0
- voiceml-0.7.1/src/voiceml/resources/_base.py +66 -0
- voiceml-0.7.1/src/voiceml/resources/applications.py +105 -0
- voiceml-0.7.1/src/voiceml/resources/calls.py +920 -0
- voiceml-0.7.1/src/voiceml/resources/conferences.py +418 -0
- voiceml-0.7.1/src/voiceml/resources/diagnostics.py +54 -0
- voiceml-0.7.1/src/voiceml/resources/incoming_phone_numbers.py +588 -0
- voiceml-0.7.1/src/voiceml/resources/messages.py +277 -0
- voiceml-0.7.1/src/voiceml/resources/notifications.py +101 -0
- voiceml-0.7.1/src/voiceml/resources/queues.py +208 -0
- voiceml-0.7.1/src/voiceml/resources/recordings.py +196 -0
voiceml-0.7.1/.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Build / packaging
|
|
2
|
+
build/
|
|
3
|
+
dist/
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
|
|
7
|
+
# Bytecode + caches
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.pyc
|
|
10
|
+
*.pyo
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
.coverage
|
|
15
|
+
htmlcov/
|
|
16
|
+
|
|
17
|
+
# Virtualenvs
|
|
18
|
+
.venv/
|
|
19
|
+
venv/
|
|
20
|
+
env/
|
|
21
|
+
|
|
22
|
+
# OS / editor
|
|
23
|
+
.DS_Store
|
|
24
|
+
.vscode/
|
|
25
|
+
.idea/
|
voiceml-0.7.1/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License with Commons Clause Restriction
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VoiceTel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
See https://voicetel.com/legal/ for the Commons Clause restriction.
|
voiceml-0.7.1/PKG-INFO
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: voiceml
|
|
3
|
+
Version: 0.7.1
|
|
4
|
+
Summary: Official Python SDK for the VoiceML REST API (Twilio-compatible voice + AMD service from VoiceTel)
|
|
5
|
+
Project-URL: Homepage, https://voiceml.voicetel.com
|
|
6
|
+
Project-URL: Repository, https://github.com/voicetel/voiceml-python-sdk
|
|
7
|
+
Project-URL: Issues, https://github.com/voicetel/voiceml-python-sdk/issues
|
|
8
|
+
Author-email: VoiceTel <support@voicetel.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: amd,telephony,twilio-compatible,voice,voiceml,voicetel
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Communications :: Telephony
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: pydantic>=2.6
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# π VoiceML Python SDK
|
|
35
|
+
|
|
36
|
+
The official Python client for the [VoiceML REST API](https://voicetel.com/docs/api/v0.6/voiceml/) β Twilio-compatible outbound voice and answering-machine-detection from VoiceTel, with type-safe, async-ready Python.
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+

|
|
40
|
+

|
|
41
|
+

|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
## π Table of Contents
|
|
45
|
+
|
|
46
|
+
- [Features](#-features)
|
|
47
|
+
- [Installation](#-installation)
|
|
48
|
+
- [Quickstart](#-quickstart)
|
|
49
|
+
- [Authentication](#-authentication)
|
|
50
|
+
- [Resource Reference](#-resource-reference)
|
|
51
|
+
- [Error Handling](#-error-handling)
|
|
52
|
+
- [Async Support](#-async-support)
|
|
53
|
+
- [Pagination](#-pagination)
|
|
54
|
+
- [Migration from twilio-python](#-migration-from-twilio-python)
|
|
55
|
+
- [Rate Limits](#-rate-limits)
|
|
56
|
+
- [Development](#-development)
|
|
57
|
+
- [API Documentation](#-api-documentation)
|
|
58
|
+
- [Contributors](#-contributors)
|
|
59
|
+
- [Sponsors](#-sponsors)
|
|
60
|
+
- [License](#-license)
|
|
61
|
+
|
|
62
|
+
## β¨ Features
|
|
63
|
+
|
|
64
|
+
### π‘οΈ Strongly Typed End-to-End
|
|
65
|
+
- **Pydantic v2 models** for every one of the 81 API operations β request bodies validated before they leave your machine, responses validated when they arrive.
|
|
66
|
+
- **Autocomplete everywhere.** Your IDE knows the shape of every field β `Call.sid`, `Recording.duration`, `Queue.current_size` are all typed.
|
|
67
|
+
- **Twilio-compatible wire shapes** β `account_sid`, `from_number`, `to_number`, status callbacks, pagination envelopes β match what Twilio's Programmable Voice API documents.
|
|
68
|
+
|
|
69
|
+
### β‘ Sync + Async, Same Surface
|
|
70
|
+
- `Client` for blocking calls, `AsyncClient` for `await`-based async β identical method names, identical return types.
|
|
71
|
+
- Built on `httpx` β HTTP/2 ready, connection pooling, custom transports if you need them.
|
|
72
|
+
- TLS session cache + persistent connections out of the box.
|
|
73
|
+
|
|
74
|
+
### π Production-Grade Transport
|
|
75
|
+
- **Automatic retry** with exponential backoff on 429 / 5xx β honors `Retry-After` headers.
|
|
76
|
+
- **Configurable timeouts** per client or per call.
|
|
77
|
+
- **HTTP Basic auth** with `AccountSid:ApiKey` β exactly what the Twilio SDK uses, so existing credentials work unchanged.
|
|
78
|
+
- **Structured exception hierarchy** β `RateLimitError`, `AuthenticationError`, `NotFoundError`, etc. all subclasses of `ApiError` you can catch broadly or narrowly.
|
|
79
|
+
|
|
80
|
+
### π Complete API Coverage
|
|
81
|
+
- **Calls** β originate, fetch, terminate, update + per-call recordings, streams, siprec, transcriptions, notifications, events, and the `/Calls/{sid}/Payments` lifecycle (Pay TwiML companion).
|
|
82
|
+
- **Conferences** β list, fetch, end conferences, plus participants (mute / hold / kick) and conference-scoped recordings.
|
|
83
|
+
- **Queues** β create, list, update, delete, peek, dequeue (front or specific member).
|
|
84
|
+
- **Applications** β CRUD on stored TwiML + callback bundles.
|
|
85
|
+
- **Recordings** β account-wide list, metadata fetch, audio fetch (follows S3 redirect), delete.
|
|
86
|
+
- **Messages** β create, fetch, list (To/From/DateSent filters + pagination), update (Body redaction; Status=canceled), delete.
|
|
87
|
+
- **IncomingPhoneNumbers** β list, fetch, update.
|
|
88
|
+
- **Notifications** β fetch, list.
|
|
89
|
+
- **Diagnostics** β `/health` deep probe, OpenAPI spec.
|
|
90
|
+
|
|
91
|
+
### π§ͺ Tested
|
|
92
|
+
- **57 unit tests** with mocked HTTP layer (`respx`) and real Pydantic validation on every fixture β spec drift gets caught at parse time.
|
|
93
|
+
- **Integration test suite** that runs against a callBroadcast / VoiceML instance β gated by env vars, safe for CI.
|
|
94
|
+
|
|
95
|
+
### π¦ Clean Distribution
|
|
96
|
+
- Zero codegen footprint β every byte hand-written.
|
|
97
|
+
- Built with `hatchling`; ships as wheel + sdist.
|
|
98
|
+
- `py.typed` marker β downstream type checkers see your imports natively.
|
|
99
|
+
|
|
100
|
+
## π Installation
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pip install voiceml
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Requires Python 3.10 or later. Tested against 3.10, 3.11, 3.12, and 3.13.
|
|
107
|
+
|
|
108
|
+
## π Quickstart
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from voiceml import Client
|
|
112
|
+
from voiceml.models import CreateCallRequest
|
|
113
|
+
|
|
114
|
+
with Client(account_sid="ACβ¦", api_key="β¦") as c:
|
|
115
|
+
call = c.calls.create(
|
|
116
|
+
CreateCallRequest(
|
|
117
|
+
To="+18005551234",
|
|
118
|
+
From="+18005550000",
|
|
119
|
+
Url="https://example.com/twiml",
|
|
120
|
+
MachineDetection="DetectMessageEnd",
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
print(call.sid, call.status)
|
|
124
|
+
|
|
125
|
+
for q in c.queues.list().queues:
|
|
126
|
+
print(q.friendly_name, q.current_size)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## π Authentication
|
|
130
|
+
|
|
131
|
+
Every endpoint uses **HTTP Basic** with your `AccountSid` as the username and your per-tenant API key as the password β identical to Twilio's auth shape, so credentials issued for Twilio code work here unchanged.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from voiceml import Client
|
|
135
|
+
|
|
136
|
+
with Client(account_sid="ACβ¦", api_key="β¦") as c:
|
|
137
|
+
me = c.diagnostics.health() # uses your AccountSid + key on every call
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> Don't have credentials yet? See **[voicetel.com/docs/api/v0.6/voiceml/](https://voicetel.com/docs/api/v0.6/voiceml/)** for issuance and rotation.
|
|
141
|
+
|
|
142
|
+
## πΊοΈ Resource Reference
|
|
143
|
+
|
|
144
|
+
| Resource | Sync + Async | Covers |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `client.calls` | originate, fetch, list, terminate, update | + per-call recordings, streams, siprec, transcriptions, notifications, events, payments |
|
|
147
|
+
| `client.conferences` | list, fetch, end | participants (mute / hold / kick), conference-scoped recordings |
|
|
148
|
+
| `client.queues` | create, list, update, delete | peek, dequeue (front or specific member) |
|
|
149
|
+
| `client.applications` | CRUD on TwiML + callback bundles | |
|
|
150
|
+
| `client.recordings` | account-wide list, metadata, audio fetch, delete | follows S3 redirect for audio |
|
|
151
|
+
| `client.messages` | create, fetch, list, update, delete | To/From/DateSent filters; Body redaction; Status=canceled |
|
|
152
|
+
| `client.incoming_phone_numbers` | list, fetch, update | |
|
|
153
|
+
| `client.notifications` | fetch, list | |
|
|
154
|
+
| `client.diagnostics` | `/health`, OpenAPI spec | |
|
|
155
|
+
|
|
156
|
+
Every method that takes a request body accepts a typed Pydantic model imported from `voiceml.models`:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from voiceml import Client
|
|
160
|
+
from voiceml.models import CreateCallRequest, StartPaymentRequest
|
|
161
|
+
|
|
162
|
+
with Client(account_sid="ACβ¦", api_key="β¦") as c:
|
|
163
|
+
call = c.calls.create(CreateCallRequest(
|
|
164
|
+
To="+18005551234",
|
|
165
|
+
From="+18005550000",
|
|
166
|
+
Url="https://example.com/twiml",
|
|
167
|
+
))
|
|
168
|
+
# On a live call, open a Pay session:
|
|
169
|
+
session = c.calls.start_payment(call.sid, StartPaymentRequest(
|
|
170
|
+
IdempotencyKey="order-482917",
|
|
171
|
+
StatusCallback="https://example.com/pay-status",
|
|
172
|
+
))
|
|
173
|
+
print(session.sid, session.status)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## π¨ Error Handling
|
|
177
|
+
|
|
178
|
+
All HTTP errors raise subclasses of `voiceml.ApiError`. Catch broadly or narrowly:
|
|
179
|
+
|
|
180
|
+
| Status | Exception |
|
|
181
|
+
|--------|-----------|
|
|
182
|
+
| 400 | `BadRequestError` |
|
|
183
|
+
| 401 | `AuthenticationError` |
|
|
184
|
+
| 403 | `PermissionDeniedError` |
|
|
185
|
+
| 404 | `NotFoundError` |
|
|
186
|
+
| 409 | `ConflictError` |
|
|
187
|
+
| 410 | `GoneError` |
|
|
188
|
+
| 429 | `RateLimitError` |
|
|
189
|
+
| 501 | `NotImplementedAPIError` |
|
|
190
|
+
| 5xx | `ServerError` |
|
|
191
|
+
| other | `ApiError` |
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from voiceml import Client, NotFoundError, RateLimitError
|
|
195
|
+
|
|
196
|
+
with Client(account_sid="ACβ¦", api_key="β¦") as c:
|
|
197
|
+
try:
|
|
198
|
+
call = c.calls.get("CA0000000000000000000000000000aaaa")
|
|
199
|
+
except NotFoundError:
|
|
200
|
+
print("That call isn't on your account.")
|
|
201
|
+
except RateLimitError as e:
|
|
202
|
+
print(f"Slow down β retry in {e.body.get('retry_after', '?')}s")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The Twilio-compatible error body (`code`, `message`, `more_info`, `status`) is parsed into `error.code` / `error.message` with the raw payload on `error.body`.
|
|
206
|
+
|
|
207
|
+
## β‘ Async Support
|
|
208
|
+
|
|
209
|
+
Identical surface to `Client`, with `await`-based methods:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
import asyncio
|
|
213
|
+
from voiceml import AsyncClient
|
|
214
|
+
|
|
215
|
+
async def main() -> None:
|
|
216
|
+
async with AsyncClient(account_sid="ACβ¦", api_key="β¦") as c:
|
|
217
|
+
calls = await c.calls.list(status="in-progress")
|
|
218
|
+
for call in calls.calls:
|
|
219
|
+
print(call.sid, call.duration)
|
|
220
|
+
|
|
221
|
+
asyncio.run(main())
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## π Pagination
|
|
225
|
+
|
|
226
|
+
List operations return a `β¦List` model with a Twilio-compatible pagination envelope (`page`, `page_size`, `total`, `next_page_uri`, `previous_page_uri`, β¦). For `/Calls` and `/Messages`, use the `iter()` helper to walk all pages transparently:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
for call in c.calls.iter(status="completed", page_size=200):
|
|
230
|
+
process(call)
|
|
231
|
+
|
|
232
|
+
for msg in c.messages.iter(from_number="+18005550000", page_size=200):
|
|
233
|
+
archive(msg)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
For other resources, page manually with `client.<resource>.list(page=n)`.
|
|
237
|
+
|
|
238
|
+
## π Migration from twilio-python
|
|
239
|
+
|
|
240
|
+
The `account_sid` + `api_key` pair Twilio's SDK validates in its constructor works unchanged here:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
# Before β Twilio
|
|
244
|
+
from twilio.rest import Client as TwilioClient
|
|
245
|
+
client = TwilioClient("ACβ¦", "<token>", region=None)
|
|
246
|
+
|
|
247
|
+
# After β VoiceML (Twilio-compatible)
|
|
248
|
+
from voiceml import Client
|
|
249
|
+
client = Client(account_sid="ACβ¦", api_key="<api-key>")
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Method names follow the resource map above (`client.calls.create(...)`, `client.queues.list()`, β¦) rather than Twilio's `client.api.v2010.accounts(sid).calls.create(...)` chain β flatter, fewer keystrokes, same wire format on the way out.
|
|
253
|
+
|
|
254
|
+
## β±οΈ Rate Limits
|
|
255
|
+
|
|
256
|
+
VoiceML applies per-tenant rate limits at the edge. The SDK automatically retries 429 responses with `Retry-After` honored, up to `max_retries` (default `2`). To bump it:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
Client(account_sid="ACβ¦", api_key="β¦", max_retries=4, timeout=60.0)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## π οΈ Development
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
git clone https://github.com/voicetel/voiceml-python-sdk
|
|
266
|
+
cd voiceml-python-sdk
|
|
267
|
+
python -m venv .venv && source .venv/bin/activate
|
|
268
|
+
pip install -e ".[dev]"
|
|
269
|
+
|
|
270
|
+
# Unit tests (fast, no network)
|
|
271
|
+
pytest tests/unit
|
|
272
|
+
|
|
273
|
+
# Lint + type-check
|
|
274
|
+
ruff check src tests
|
|
275
|
+
mypy src
|
|
276
|
+
|
|
277
|
+
# Integration tests (live, read-only against a configured VoiceML instance)
|
|
278
|
+
cp .env.example .env # fill in VOICEML_ACCOUNT_SID / VOICEML_API_KEY / VOICEML_BASE_URL
|
|
279
|
+
pytest tests/integration
|
|
280
|
+
|
|
281
|
+
# Build wheel + sdist
|
|
282
|
+
python -m build
|
|
283
|
+
twine check dist/*
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## π API Documentation
|
|
287
|
+
|
|
288
|
+
- **Reference docs:** [voicetel.com/docs/api/v0.6/voiceml/](https://voicetel.com/docs/api/v0.6/voiceml/)
|
|
289
|
+
- **Validator:** [voicetel.com/voiceml/validator/](https://voicetel.com/voiceml/validator/)
|
|
290
|
+
- **SDK catalogue:** [voicetel.com/docs/voiceml-sdks/](https://voicetel.com/docs/voiceml-sdks/)
|
|
291
|
+
- **Type definitions:** see the `voiceml.models` module β every wire shape has a Pydantic model.
|
|
292
|
+
|
|
293
|
+
## π Contributors
|
|
294
|
+
|
|
295
|
+
- [Michael Mavroudis](https://github.com/mavroudis) β Lead Developer
|
|
296
|
+
|
|
297
|
+
Contributions welcome. Open an issue describing the change you want to make, or send a pull request against `main`.
|
|
298
|
+
|
|
299
|
+
## π Sponsors
|
|
300
|
+
|
|
301
|
+
| Sponsor | Contribution |
|
|
302
|
+
|---------|--------------|
|
|
303
|
+
| [VoiceTel Communications](https://voicetel.com) | Primary development and production hosting |
|
|
304
|
+
|
|
305
|
+
## π License
|
|
306
|
+
|
|
307
|
+
MIT with the Commons Clause restriction. See [LICENSE](LICENSE) and [voicetel.com/legal/](https://voicetel.com/legal/).
|
voiceml-0.7.1/README.md
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# π VoiceML Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python client for the [VoiceML REST API](https://voicetel.com/docs/api/v0.6/voiceml/) β Twilio-compatible outbound voice and answering-machine-detection from VoiceTel, with type-safe, async-ready Python.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## π Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Features](#-features)
|
|
14
|
+
- [Installation](#-installation)
|
|
15
|
+
- [Quickstart](#-quickstart)
|
|
16
|
+
- [Authentication](#-authentication)
|
|
17
|
+
- [Resource Reference](#-resource-reference)
|
|
18
|
+
- [Error Handling](#-error-handling)
|
|
19
|
+
- [Async Support](#-async-support)
|
|
20
|
+
- [Pagination](#-pagination)
|
|
21
|
+
- [Migration from twilio-python](#-migration-from-twilio-python)
|
|
22
|
+
- [Rate Limits](#-rate-limits)
|
|
23
|
+
- [Development](#-development)
|
|
24
|
+
- [API Documentation](#-api-documentation)
|
|
25
|
+
- [Contributors](#-contributors)
|
|
26
|
+
- [Sponsors](#-sponsors)
|
|
27
|
+
- [License](#-license)
|
|
28
|
+
|
|
29
|
+
## β¨ Features
|
|
30
|
+
|
|
31
|
+
### π‘οΈ Strongly Typed End-to-End
|
|
32
|
+
- **Pydantic v2 models** for every one of the 81 API operations β request bodies validated before they leave your machine, responses validated when they arrive.
|
|
33
|
+
- **Autocomplete everywhere.** Your IDE knows the shape of every field β `Call.sid`, `Recording.duration`, `Queue.current_size` are all typed.
|
|
34
|
+
- **Twilio-compatible wire shapes** β `account_sid`, `from_number`, `to_number`, status callbacks, pagination envelopes β match what Twilio's Programmable Voice API documents.
|
|
35
|
+
|
|
36
|
+
### β‘ Sync + Async, Same Surface
|
|
37
|
+
- `Client` for blocking calls, `AsyncClient` for `await`-based async β identical method names, identical return types.
|
|
38
|
+
- Built on `httpx` β HTTP/2 ready, connection pooling, custom transports if you need them.
|
|
39
|
+
- TLS session cache + persistent connections out of the box.
|
|
40
|
+
|
|
41
|
+
### π Production-Grade Transport
|
|
42
|
+
- **Automatic retry** with exponential backoff on 429 / 5xx β honors `Retry-After` headers.
|
|
43
|
+
- **Configurable timeouts** per client or per call.
|
|
44
|
+
- **HTTP Basic auth** with `AccountSid:ApiKey` β exactly what the Twilio SDK uses, so existing credentials work unchanged.
|
|
45
|
+
- **Structured exception hierarchy** β `RateLimitError`, `AuthenticationError`, `NotFoundError`, etc. all subclasses of `ApiError` you can catch broadly or narrowly.
|
|
46
|
+
|
|
47
|
+
### π Complete API Coverage
|
|
48
|
+
- **Calls** β originate, fetch, terminate, update + per-call recordings, streams, siprec, transcriptions, notifications, events, and the `/Calls/{sid}/Payments` lifecycle (Pay TwiML companion).
|
|
49
|
+
- **Conferences** β list, fetch, end conferences, plus participants (mute / hold / kick) and conference-scoped recordings.
|
|
50
|
+
- **Queues** β create, list, update, delete, peek, dequeue (front or specific member).
|
|
51
|
+
- **Applications** β CRUD on stored TwiML + callback bundles.
|
|
52
|
+
- **Recordings** β account-wide list, metadata fetch, audio fetch (follows S3 redirect), delete.
|
|
53
|
+
- **Messages** β create, fetch, list (To/From/DateSent filters + pagination), update (Body redaction; Status=canceled), delete.
|
|
54
|
+
- **IncomingPhoneNumbers** β list, fetch, update.
|
|
55
|
+
- **Notifications** β fetch, list.
|
|
56
|
+
- **Diagnostics** β `/health` deep probe, OpenAPI spec.
|
|
57
|
+
|
|
58
|
+
### π§ͺ Tested
|
|
59
|
+
- **57 unit tests** with mocked HTTP layer (`respx`) and real Pydantic validation on every fixture β spec drift gets caught at parse time.
|
|
60
|
+
- **Integration test suite** that runs against a callBroadcast / VoiceML instance β gated by env vars, safe for CI.
|
|
61
|
+
|
|
62
|
+
### π¦ Clean Distribution
|
|
63
|
+
- Zero codegen footprint β every byte hand-written.
|
|
64
|
+
- Built with `hatchling`; ships as wheel + sdist.
|
|
65
|
+
- `py.typed` marker β downstream type checkers see your imports natively.
|
|
66
|
+
|
|
67
|
+
## π Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install voiceml
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Requires Python 3.10 or later. Tested against 3.10, 3.11, 3.12, and 3.13.
|
|
74
|
+
|
|
75
|
+
## π Quickstart
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from voiceml import Client
|
|
79
|
+
from voiceml.models import CreateCallRequest
|
|
80
|
+
|
|
81
|
+
with Client(account_sid="ACβ¦", api_key="β¦") as c:
|
|
82
|
+
call = c.calls.create(
|
|
83
|
+
CreateCallRequest(
|
|
84
|
+
To="+18005551234",
|
|
85
|
+
From="+18005550000",
|
|
86
|
+
Url="https://example.com/twiml",
|
|
87
|
+
MachineDetection="DetectMessageEnd",
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
print(call.sid, call.status)
|
|
91
|
+
|
|
92
|
+
for q in c.queues.list().queues:
|
|
93
|
+
print(q.friendly_name, q.current_size)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## π Authentication
|
|
97
|
+
|
|
98
|
+
Every endpoint uses **HTTP Basic** with your `AccountSid` as the username and your per-tenant API key as the password β identical to Twilio's auth shape, so credentials issued for Twilio code work here unchanged.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from voiceml import Client
|
|
102
|
+
|
|
103
|
+
with Client(account_sid="ACβ¦", api_key="β¦") as c:
|
|
104
|
+
me = c.diagnostics.health() # uses your AccountSid + key on every call
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> Don't have credentials yet? See **[voicetel.com/docs/api/v0.6/voiceml/](https://voicetel.com/docs/api/v0.6/voiceml/)** for issuance and rotation.
|
|
108
|
+
|
|
109
|
+
## πΊοΈ Resource Reference
|
|
110
|
+
|
|
111
|
+
| Resource | Sync + Async | Covers |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `client.calls` | originate, fetch, list, terminate, update | + per-call recordings, streams, siprec, transcriptions, notifications, events, payments |
|
|
114
|
+
| `client.conferences` | list, fetch, end | participants (mute / hold / kick), conference-scoped recordings |
|
|
115
|
+
| `client.queues` | create, list, update, delete | peek, dequeue (front or specific member) |
|
|
116
|
+
| `client.applications` | CRUD on TwiML + callback bundles | |
|
|
117
|
+
| `client.recordings` | account-wide list, metadata, audio fetch, delete | follows S3 redirect for audio |
|
|
118
|
+
| `client.messages` | create, fetch, list, update, delete | To/From/DateSent filters; Body redaction; Status=canceled |
|
|
119
|
+
| `client.incoming_phone_numbers` | list, fetch, update | |
|
|
120
|
+
| `client.notifications` | fetch, list | |
|
|
121
|
+
| `client.diagnostics` | `/health`, OpenAPI spec | |
|
|
122
|
+
|
|
123
|
+
Every method that takes a request body accepts a typed Pydantic model imported from `voiceml.models`:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from voiceml import Client
|
|
127
|
+
from voiceml.models import CreateCallRequest, StartPaymentRequest
|
|
128
|
+
|
|
129
|
+
with Client(account_sid="ACβ¦", api_key="β¦") as c:
|
|
130
|
+
call = c.calls.create(CreateCallRequest(
|
|
131
|
+
To="+18005551234",
|
|
132
|
+
From="+18005550000",
|
|
133
|
+
Url="https://example.com/twiml",
|
|
134
|
+
))
|
|
135
|
+
# On a live call, open a Pay session:
|
|
136
|
+
session = c.calls.start_payment(call.sid, StartPaymentRequest(
|
|
137
|
+
IdempotencyKey="order-482917",
|
|
138
|
+
StatusCallback="https://example.com/pay-status",
|
|
139
|
+
))
|
|
140
|
+
print(session.sid, session.status)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## π¨ Error Handling
|
|
144
|
+
|
|
145
|
+
All HTTP errors raise subclasses of `voiceml.ApiError`. Catch broadly or narrowly:
|
|
146
|
+
|
|
147
|
+
| Status | Exception |
|
|
148
|
+
|--------|-----------|
|
|
149
|
+
| 400 | `BadRequestError` |
|
|
150
|
+
| 401 | `AuthenticationError` |
|
|
151
|
+
| 403 | `PermissionDeniedError` |
|
|
152
|
+
| 404 | `NotFoundError` |
|
|
153
|
+
| 409 | `ConflictError` |
|
|
154
|
+
| 410 | `GoneError` |
|
|
155
|
+
| 429 | `RateLimitError` |
|
|
156
|
+
| 501 | `NotImplementedAPIError` |
|
|
157
|
+
| 5xx | `ServerError` |
|
|
158
|
+
| other | `ApiError` |
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from voiceml import Client, NotFoundError, RateLimitError
|
|
162
|
+
|
|
163
|
+
with Client(account_sid="ACβ¦", api_key="β¦") as c:
|
|
164
|
+
try:
|
|
165
|
+
call = c.calls.get("CA0000000000000000000000000000aaaa")
|
|
166
|
+
except NotFoundError:
|
|
167
|
+
print("That call isn't on your account.")
|
|
168
|
+
except RateLimitError as e:
|
|
169
|
+
print(f"Slow down β retry in {e.body.get('retry_after', '?')}s")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The Twilio-compatible error body (`code`, `message`, `more_info`, `status`) is parsed into `error.code` / `error.message` with the raw payload on `error.body`.
|
|
173
|
+
|
|
174
|
+
## β‘ Async Support
|
|
175
|
+
|
|
176
|
+
Identical surface to `Client`, with `await`-based methods:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
import asyncio
|
|
180
|
+
from voiceml import AsyncClient
|
|
181
|
+
|
|
182
|
+
async def main() -> None:
|
|
183
|
+
async with AsyncClient(account_sid="ACβ¦", api_key="β¦") as c:
|
|
184
|
+
calls = await c.calls.list(status="in-progress")
|
|
185
|
+
for call in calls.calls:
|
|
186
|
+
print(call.sid, call.duration)
|
|
187
|
+
|
|
188
|
+
asyncio.run(main())
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## π Pagination
|
|
192
|
+
|
|
193
|
+
List operations return a `β¦List` model with a Twilio-compatible pagination envelope (`page`, `page_size`, `total`, `next_page_uri`, `previous_page_uri`, β¦). For `/Calls` and `/Messages`, use the `iter()` helper to walk all pages transparently:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
for call in c.calls.iter(status="completed", page_size=200):
|
|
197
|
+
process(call)
|
|
198
|
+
|
|
199
|
+
for msg in c.messages.iter(from_number="+18005550000", page_size=200):
|
|
200
|
+
archive(msg)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
For other resources, page manually with `client.<resource>.list(page=n)`.
|
|
204
|
+
|
|
205
|
+
## π Migration from twilio-python
|
|
206
|
+
|
|
207
|
+
The `account_sid` + `api_key` pair Twilio's SDK validates in its constructor works unchanged here:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
# Before β Twilio
|
|
211
|
+
from twilio.rest import Client as TwilioClient
|
|
212
|
+
client = TwilioClient("ACβ¦", "<token>", region=None)
|
|
213
|
+
|
|
214
|
+
# After β VoiceML (Twilio-compatible)
|
|
215
|
+
from voiceml import Client
|
|
216
|
+
client = Client(account_sid="ACβ¦", api_key="<api-key>")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Method names follow the resource map above (`client.calls.create(...)`, `client.queues.list()`, β¦) rather than Twilio's `client.api.v2010.accounts(sid).calls.create(...)` chain β flatter, fewer keystrokes, same wire format on the way out.
|
|
220
|
+
|
|
221
|
+
## β±οΈ Rate Limits
|
|
222
|
+
|
|
223
|
+
VoiceML applies per-tenant rate limits at the edge. The SDK automatically retries 429 responses with `Retry-After` honored, up to `max_retries` (default `2`). To bump it:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
Client(account_sid="ACβ¦", api_key="β¦", max_retries=4, timeout=60.0)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## π οΈ Development
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
git clone https://github.com/voicetel/voiceml-python-sdk
|
|
233
|
+
cd voiceml-python-sdk
|
|
234
|
+
python -m venv .venv && source .venv/bin/activate
|
|
235
|
+
pip install -e ".[dev]"
|
|
236
|
+
|
|
237
|
+
# Unit tests (fast, no network)
|
|
238
|
+
pytest tests/unit
|
|
239
|
+
|
|
240
|
+
# Lint + type-check
|
|
241
|
+
ruff check src tests
|
|
242
|
+
mypy src
|
|
243
|
+
|
|
244
|
+
# Integration tests (live, read-only against a configured VoiceML instance)
|
|
245
|
+
cp .env.example .env # fill in VOICEML_ACCOUNT_SID / VOICEML_API_KEY / VOICEML_BASE_URL
|
|
246
|
+
pytest tests/integration
|
|
247
|
+
|
|
248
|
+
# Build wheel + sdist
|
|
249
|
+
python -m build
|
|
250
|
+
twine check dist/*
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## π API Documentation
|
|
254
|
+
|
|
255
|
+
- **Reference docs:** [voicetel.com/docs/api/v0.6/voiceml/](https://voicetel.com/docs/api/v0.6/voiceml/)
|
|
256
|
+
- **Validator:** [voicetel.com/voiceml/validator/](https://voicetel.com/voiceml/validator/)
|
|
257
|
+
- **SDK catalogue:** [voicetel.com/docs/voiceml-sdks/](https://voicetel.com/docs/voiceml-sdks/)
|
|
258
|
+
- **Type definitions:** see the `voiceml.models` module β every wire shape has a Pydantic model.
|
|
259
|
+
|
|
260
|
+
## π Contributors
|
|
261
|
+
|
|
262
|
+
- [Michael Mavroudis](https://github.com/mavroudis) β Lead Developer
|
|
263
|
+
|
|
264
|
+
Contributions welcome. Open an issue describing the change you want to make, or send a pull request against `main`.
|
|
265
|
+
|
|
266
|
+
## π Sponsors
|
|
267
|
+
|
|
268
|
+
| Sponsor | Contribution |
|
|
269
|
+
|---------|--------------|
|
|
270
|
+
| [VoiceTel Communications](https://voicetel.com) | Primary development and production hosting |
|
|
271
|
+
|
|
272
|
+
## π License
|
|
273
|
+
|
|
274
|
+
MIT with the Commons Clause restriction. See [LICENSE](LICENSE) and [voicetel.com/legal/](https://voicetel.com/legal/).
|