sempera 0.1.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.
- sempera-0.1.0/LICENSE +21 -0
- sempera-0.1.0/PKG-INFO +162 -0
- sempera-0.1.0/README.md +145 -0
- sempera-0.1.0/pyproject.toml +26 -0
- sempera-0.1.0/sempera/__init__.py +29 -0
- sempera-0.1.0/sempera/_generated.py +4530 -0
- sempera-0.1.0/sempera/_runtime.py +210 -0
- sempera-0.1.0/sempera/webhooks.py +49 -0
- sempera-0.1.0/sempera.egg-info/PKG-INFO +162 -0
- sempera-0.1.0/sempera.egg-info/SOURCES.txt +12 -0
- sempera-0.1.0/sempera.egg-info/dependency_links.txt +1 -0
- sempera-0.1.0/sempera.egg-info/top_level.txt +1 -0
- sempera-0.1.0/setup.cfg +4 -0
- sempera-0.1.0/tests/test_sempera.py +229 -0
sempera-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sempera Labs, Inc.
|
|
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.
|
sempera-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sempera
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The official Python client for the Sempera API: a method for every operation, retries, idempotency keys, paging and webhook verification
|
|
5
|
+
Author-email: "Sempera Labs, Inc." <hello@sempera.org>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Sempera-AI/sempera-python
|
|
8
|
+
Project-URL: Issues, https://github.com/Sempera-AI/sempera-python/issues
|
|
9
|
+
Keywords: sempera,voice,phone,ai,sdk
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# sempera
|
|
19
|
+
|
|
20
|
+
[Documentation](https://docs.sempera.org) | [SDKs](https://docs.sempera.org/sdks) | [OpenAPI document](https://api.sempera.org/openapi.json) | [MIT licence](LICENSE)
|
|
21
|
+
|
|
22
|
+
The official Python client for the [Sempera](https://sempera.org) API at `https://api.sempera.org`.
|
|
23
|
+
|
|
24
|
+
* One method for every operation in the public OpenAPI document (`https://api.sempera.org/openapi.json`), generated from it, so the client follows the API.
|
|
25
|
+
* A small hand-written core: bearer-key authentication, typed errors, retries with exponential backoff, automatic idempotency keys, cursor paging and webhook signature verification.
|
|
26
|
+
* Standard library only. Python 3.9 or newer.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install sempera
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
Create an API key in the Sempera App and keep it on your server. Pass it to the constructor, or set `SEMPERA_API_KEY` and omit it.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from sempera import Sempera
|
|
40
|
+
|
|
41
|
+
client = Sempera("sempera_sk_example") # or Sempera() with SEMPERA_API_KEY set
|
|
42
|
+
print(client.get_me())
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Methods are named after the operation id in the contract, in snake_case (`list_agents`, `create_call`, `send_sms`). Path and query parameters are named exactly as in the contract. A request body is a plain dict, passed first. Answers are plain dicts.
|
|
46
|
+
|
|
47
|
+
### Agents
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from sempera import paginate
|
|
51
|
+
|
|
52
|
+
page = client.list_agents(limit=20)
|
|
53
|
+
for agent in page["data"]:
|
|
54
|
+
print(agent["id"])
|
|
55
|
+
|
|
56
|
+
# Or follow every page:
|
|
57
|
+
for agent in paginate(lambda cursor: client.list_agents(limit=100, cursor=cursor)):
|
|
58
|
+
print(agent["id"])
|
|
59
|
+
|
|
60
|
+
one = client.get_agent("agt_example")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Calls
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
call = client.create_call(
|
|
67
|
+
{
|
|
68
|
+
"agent_id": "agt_example",
|
|
69
|
+
"mode": "prompt",
|
|
70
|
+
"phone_number": "+14155550123",
|
|
71
|
+
"task": "Confirm Friday at 3pm. Ask whether that time still suits.",
|
|
72
|
+
"consent_basis": "This person asked us to call them about Friday.",
|
|
73
|
+
},
|
|
74
|
+
idempotency_key="friday-3pm-ada",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
same = client.get_call(call["id"])
|
|
78
|
+
recent = client.list_calls(agent_id="agt_example", limit=10)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Phone numbers and text messages
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
numbers = client.list_phone_numbers(agent_id="agt_example")
|
|
85
|
+
|
|
86
|
+
client.send_sms({
|
|
87
|
+
"agent_id": "agt_example",
|
|
88
|
+
"agent_number": "+14155550100",
|
|
89
|
+
"user_number": "+14155550123",
|
|
90
|
+
"agent_message": "Your table is confirmed for Friday at 3pm.",
|
|
91
|
+
"consent_basis": "This person asked us to text them a confirmation.",
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Conversations
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
threads = client.messaging_conversations("agt_example", "sms")
|
|
99
|
+
thread = client.messaging_conversation("agt_example", "sms", "conv_example")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Knowledge sources
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
sources = client.list_knowledge_sources(agent_id="agt_example")
|
|
106
|
+
client.create_text_knowledge_source({
|
|
107
|
+
"agent_id": "agt_example",
|
|
108
|
+
"name": "Opening hours",
|
|
109
|
+
"content": "Open 9 to 5, Monday to Friday.",
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Webhooks
|
|
114
|
+
|
|
115
|
+
Verify every delivery against the raw request body before you parse it. The helper checks the `X-Sempera-Signature` header (`t=<unix seconds>,v1=<hex HMAC-SHA256 over "<t>." + body>`) in constant time and rejects a timestamp more than 300 seconds from your clock. Answer `400` and process nothing when it raises.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from sempera import WebhookSignatureError, construct_event
|
|
119
|
+
|
|
120
|
+
def handle(raw_body: bytes, signature_header: str) -> int:
|
|
121
|
+
try:
|
|
122
|
+
event = construct_event("whsec_example", raw_body, signature_header)
|
|
123
|
+
except WebhookSignatureError:
|
|
124
|
+
return 400
|
|
125
|
+
print(event["type"], event["id"])
|
|
126
|
+
return 200
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Errors
|
|
130
|
+
|
|
131
|
+
Every refusal raises a typed error carrying `status`, the stable `code`, an optional `reason` and `field`, and the `request_id` to quote to support. Branch on `code` or `reason`, never on the message.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from sempera import NotFoundError, RateLimitError, SemperaError
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
client.get_agent("agt_missing")
|
|
138
|
+
except NotFoundError as err:
|
|
139
|
+
print(err.code, err.request_id)
|
|
140
|
+
except RateLimitError as err:
|
|
141
|
+
print("retry in", err.retry_after)
|
|
142
|
+
except SemperaError as err:
|
|
143
|
+
print(err.status, err.code, err.reason)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Classes: `ValidationError` (400), `AuthenticationError` (401), `PermissionDeniedError` (403), `NotFoundError` (404), `ConflictError` (409), `IdempotencyKeyReusedError` (409), `RateLimitError` (429), `ServerError` (5xx) and `ConnectionFailure` (no answer).
|
|
147
|
+
|
|
148
|
+
## Retries and idempotency
|
|
149
|
+
|
|
150
|
+
The client retries a `429`, a `502`, a `504`, a `503` with code `service_unavailable` and a dropped connection, two times by default, with exponential backoff and jitter, waiting the `Retry-After` it is sent. Change the count with `max_retries` on the client or on one call. A write is retried only when it carries an idempotency key (generated for you, and reused by every retry) or after a `429`. Pass your own `idempotency_key` to make a retry safe across process restarts.
|
|
151
|
+
|
|
152
|
+
## Develop
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
python -m unittest discover -s tests
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The tests replace the HTTP layer with a scripted fake, so they need no network and no key.
|
|
159
|
+
|
|
160
|
+
## Licence
|
|
161
|
+
|
|
162
|
+
MIT. Copyright Sempera Labs, Inc.
|
sempera-0.1.0/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# sempera
|
|
2
|
+
|
|
3
|
+
[Documentation](https://docs.sempera.org) | [SDKs](https://docs.sempera.org/sdks) | [OpenAPI document](https://api.sempera.org/openapi.json) | [MIT licence](LICENSE)
|
|
4
|
+
|
|
5
|
+
The official Python client for the [Sempera](https://sempera.org) API at `https://api.sempera.org`.
|
|
6
|
+
|
|
7
|
+
* One method for every operation in the public OpenAPI document (`https://api.sempera.org/openapi.json`), generated from it, so the client follows the API.
|
|
8
|
+
* A small hand-written core: bearer-key authentication, typed errors, retries with exponential backoff, automatic idempotency keys, cursor paging and webhook signature verification.
|
|
9
|
+
* Standard library only. Python 3.9 or newer.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install sempera
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
Create an API key in the Sempera App and keep it on your server. Pass it to the constructor, or set `SEMPERA_API_KEY` and omit it.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from sempera import Sempera
|
|
23
|
+
|
|
24
|
+
client = Sempera("sempera_sk_example") # or Sempera() with SEMPERA_API_KEY set
|
|
25
|
+
print(client.get_me())
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Methods are named after the operation id in the contract, in snake_case (`list_agents`, `create_call`, `send_sms`). Path and query parameters are named exactly as in the contract. A request body is a plain dict, passed first. Answers are plain dicts.
|
|
29
|
+
|
|
30
|
+
### Agents
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from sempera import paginate
|
|
34
|
+
|
|
35
|
+
page = client.list_agents(limit=20)
|
|
36
|
+
for agent in page["data"]:
|
|
37
|
+
print(agent["id"])
|
|
38
|
+
|
|
39
|
+
# Or follow every page:
|
|
40
|
+
for agent in paginate(lambda cursor: client.list_agents(limit=100, cursor=cursor)):
|
|
41
|
+
print(agent["id"])
|
|
42
|
+
|
|
43
|
+
one = client.get_agent("agt_example")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Calls
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
call = client.create_call(
|
|
50
|
+
{
|
|
51
|
+
"agent_id": "agt_example",
|
|
52
|
+
"mode": "prompt",
|
|
53
|
+
"phone_number": "+14155550123",
|
|
54
|
+
"task": "Confirm Friday at 3pm. Ask whether that time still suits.",
|
|
55
|
+
"consent_basis": "This person asked us to call them about Friday.",
|
|
56
|
+
},
|
|
57
|
+
idempotency_key="friday-3pm-ada",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
same = client.get_call(call["id"])
|
|
61
|
+
recent = client.list_calls(agent_id="agt_example", limit=10)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Phone numbers and text messages
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
numbers = client.list_phone_numbers(agent_id="agt_example")
|
|
68
|
+
|
|
69
|
+
client.send_sms({
|
|
70
|
+
"agent_id": "agt_example",
|
|
71
|
+
"agent_number": "+14155550100",
|
|
72
|
+
"user_number": "+14155550123",
|
|
73
|
+
"agent_message": "Your table is confirmed for Friday at 3pm.",
|
|
74
|
+
"consent_basis": "This person asked us to text them a confirmation.",
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Conversations
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
threads = client.messaging_conversations("agt_example", "sms")
|
|
82
|
+
thread = client.messaging_conversation("agt_example", "sms", "conv_example")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Knowledge sources
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
sources = client.list_knowledge_sources(agent_id="agt_example")
|
|
89
|
+
client.create_text_knowledge_source({
|
|
90
|
+
"agent_id": "agt_example",
|
|
91
|
+
"name": "Opening hours",
|
|
92
|
+
"content": "Open 9 to 5, Monday to Friday.",
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Webhooks
|
|
97
|
+
|
|
98
|
+
Verify every delivery against the raw request body before you parse it. The helper checks the `X-Sempera-Signature` header (`t=<unix seconds>,v1=<hex HMAC-SHA256 over "<t>." + body>`) in constant time and rejects a timestamp more than 300 seconds from your clock. Answer `400` and process nothing when it raises.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from sempera import WebhookSignatureError, construct_event
|
|
102
|
+
|
|
103
|
+
def handle(raw_body: bytes, signature_header: str) -> int:
|
|
104
|
+
try:
|
|
105
|
+
event = construct_event("whsec_example", raw_body, signature_header)
|
|
106
|
+
except WebhookSignatureError:
|
|
107
|
+
return 400
|
|
108
|
+
print(event["type"], event["id"])
|
|
109
|
+
return 200
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Errors
|
|
113
|
+
|
|
114
|
+
Every refusal raises a typed error carrying `status`, the stable `code`, an optional `reason` and `field`, and the `request_id` to quote to support. Branch on `code` or `reason`, never on the message.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from sempera import NotFoundError, RateLimitError, SemperaError
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
client.get_agent("agt_missing")
|
|
121
|
+
except NotFoundError as err:
|
|
122
|
+
print(err.code, err.request_id)
|
|
123
|
+
except RateLimitError as err:
|
|
124
|
+
print("retry in", err.retry_after)
|
|
125
|
+
except SemperaError as err:
|
|
126
|
+
print(err.status, err.code, err.reason)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Classes: `ValidationError` (400), `AuthenticationError` (401), `PermissionDeniedError` (403), `NotFoundError` (404), `ConflictError` (409), `IdempotencyKeyReusedError` (409), `RateLimitError` (429), `ServerError` (5xx) and `ConnectionFailure` (no answer).
|
|
130
|
+
|
|
131
|
+
## Retries and idempotency
|
|
132
|
+
|
|
133
|
+
The client retries a `429`, a `502`, a `504`, a `503` with code `service_unavailable` and a dropped connection, two times by default, with exponential backoff and jitter, waiting the `Retry-After` it is sent. Change the count with `max_retries` on the client or on one call. A write is retried only when it carries an idempotency key (generated for you, and reused by every retry) or after a `429`. Pass your own `idempotency_key` to make a retry safe across process restarts.
|
|
134
|
+
|
|
135
|
+
## Develop
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
python -m unittest discover -s tests
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The tests replace the HTTP layer with a scripted fake, so they need no network and no key.
|
|
142
|
+
|
|
143
|
+
## Licence
|
|
144
|
+
|
|
145
|
+
MIT. Copyright Sempera Labs, Inc.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sempera"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "The official Python client for the Sempera API: a method for every operation, retries, idempotency keys, paging and webhook verification"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = []
|
|
12
|
+
license = { text = "MIT" }
|
|
13
|
+
authors = [{ name = "Sempera Labs, Inc.", email = "hello@sempera.org" }]
|
|
14
|
+
keywords = ["sempera", "voice", "phone", "ai", "sdk"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/Sempera-AI/sempera-python"
|
|
23
|
+
Issues = "https://github.com/Sempera-AI/sempera-python/issues"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
packages = ["sempera"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""The Sempera Python SDK. Quick start: https://api.sempera.org/docs/sdks.md."""
|
|
2
|
+
|
|
3
|
+
from ._generated import API_VERSION, DEFAULT_BASE_URL, ERROR_CODES, OPERATIONS, Operations
|
|
4
|
+
from ._runtime import (
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
ConflictError,
|
|
7
|
+
ConnectionFailure,
|
|
8
|
+
IdempotencyKeyReusedError,
|
|
9
|
+
NotFoundError,
|
|
10
|
+
PermissionDeniedError,
|
|
11
|
+
RateLimitError,
|
|
12
|
+
SemperaError,
|
|
13
|
+
ServerError,
|
|
14
|
+
ValidationError,
|
|
15
|
+
paginate,
|
|
16
|
+
)
|
|
17
|
+
from .webhooks import WebhookSignatureError, construct_event, verify_signature
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Sempera(Operations):
|
|
21
|
+
"""`Sempera(api_key)` then `client.list_agents()`; one method per operation, named in snake_case."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"API_VERSION", "DEFAULT_BASE_URL", "ERROR_CODES", "OPERATIONS", "Sempera", "paginate",
|
|
26
|
+
"SemperaError", "ValidationError", "AuthenticationError", "PermissionDeniedError", "NotFoundError",
|
|
27
|
+
"ConflictError", "IdempotencyKeyReusedError", "RateLimitError", "ServerError", "ConnectionFailure",
|
|
28
|
+
"WebhookSignatureError", "construct_event", "verify_signature",
|
|
29
|
+
]
|