unitpost 0.0.0__tar.gz → 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.
- unitpost-0.2.0/.gitignore +7 -0
- unitpost-0.2.0/PKG-INFO +178 -0
- unitpost-0.2.0/README.md +155 -0
- unitpost-0.2.0/package.json +11 -0
- unitpost-0.2.0/pyproject.toml +41 -0
- unitpost-0.2.0/src/unitpost/__init__.py +16 -0
- unitpost-0.2.0/src/unitpost/_client.py +83 -0
- unitpost-0.2.0/src/unitpost/_generated/__init__.py +5 -0
- unitpost-0.2.0/src/unitpost/_generated/models.py +2652 -0
- unitpost-0.2.0/src/unitpost/_generated/operations.py +1119 -0
- unitpost-0.2.0/src/unitpost/_http.py +237 -0
- unitpost-0.2.0/src/unitpost/_webhooks.py +87 -0
- unitpost-0.2.0/src/unitpost/resources.py +403 -0
- unitpost-0.2.0/tests/test_retry.py +156 -0
- unitpost-0.2.0/tests/test_sdk.py +139 -0
- unitpost-0.0.0/.gitignore +0 -67
- unitpost-0.0.0/PKG-INFO +0 -17
- unitpost-0.0.0/README.md +0 -6
- unitpost-0.0.0/pyproject.toml +0 -19
- unitpost-0.0.0/unitpost/__init__.py +0 -6
unitpost-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unitpost
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Unitpost SDK for Python — send email and manage contacts, segments, campaigns, templates, brand kits, domains, suppressions, webhooks, and usage.
|
|
5
|
+
Project-URL: Homepage, https://www.unitpost.com
|
|
6
|
+
Project-URL: Documentation, https://www.unitpost.com/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/unitpostcom/unitpost
|
|
8
|
+
Project-URL: Issues, https://github.com/unitpostcom/unitpost/issues
|
|
9
|
+
Author: Unitpost
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: api,email,sdk,transactional-email,unitpost
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Requires-Dist: httpx>=0.28.1
|
|
17
|
+
Requires-Dist: typing-extensions>=4.16.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: mypy>=2.3.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=9.1.1; extra == 'dev'
|
|
21
|
+
Requires-Dist: respx>=0.23.1; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# unitpost
|
|
25
|
+
|
|
26
|
+
Official [Unitpost](https://www.unitpost.com) SDK for Python. Send email and manage
|
|
27
|
+
contacts, segments, campaigns, templates, domains, suppressions, and webhooks.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install unitpost
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Python 3.8+.
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from unitpost import Unitpost
|
|
41
|
+
|
|
42
|
+
unitpost = Unitpost() # reads UNITPOST_API_KEY (or pass Unitpost("up_..."))
|
|
43
|
+
|
|
44
|
+
result = unitpost.email.send({
|
|
45
|
+
"from": "Acme <you@yourdomain.com>", # bare address works too
|
|
46
|
+
"to": "customer@example.com",
|
|
47
|
+
"subject": "Hello",
|
|
48
|
+
"html": "<p>It works!</p>",
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
if result.error:
|
|
52
|
+
print(result.error.code, result.error.message)
|
|
53
|
+
else:
|
|
54
|
+
print("sent", result.data["id"])
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Every method returns a `Result` with `.data` and `.error`. An API-level failure
|
|
58
|
+
populates `.error` (a `UnitpostError`) instead of raising.
|
|
59
|
+
|
|
60
|
+
## Authentication
|
|
61
|
+
|
|
62
|
+
Pass the key to `Unitpost(...)`, or set `UNITPOST_API_KEY`. Create a key at
|
|
63
|
+
[unitpost.com → Settings → API keys](https://www.unitpost.com).
|
|
64
|
+
|
|
65
|
+
## Client surface
|
|
66
|
+
|
|
67
|
+
| Accessor | Covers |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `emails` | Send, batch, list, get, update, stats, inbound (`received_*`) |
|
|
70
|
+
| `contacts` | CRUD, import jobs |
|
|
71
|
+
| `contact_fields` | Custom field definitions |
|
|
72
|
+
| `segments` | Segments + membership |
|
|
73
|
+
| `topics` | Topics + contact topic prefs |
|
|
74
|
+
| `campaigns` | Draft, schedule, send, pause/resume |
|
|
75
|
+
| `templates` | Template CRUD |
|
|
76
|
+
| `brand_kits` | Brand kit |
|
|
77
|
+
| `domains` | Domains + verify |
|
|
78
|
+
| `webhooks` | Endpoints + `verify()` |
|
|
79
|
+
| `api_keys` | API key management |
|
|
80
|
+
| `suppressions` | Suppression list |
|
|
81
|
+
| `usage` | Billing-period usage snapshot |
|
|
82
|
+
|
|
83
|
+
## Pagination
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
for contact in unitpost.contacts.list_all():
|
|
87
|
+
print(contact["email"])
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Suppressions
|
|
91
|
+
|
|
92
|
+
The suppression list holds addresses excluded from every send — single, batch,
|
|
93
|
+
and campaign, including cc/bcc — case-insensitively. Hard bounces and spam
|
|
94
|
+
complaints are added automatically; you can also add your own:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# Suppress one address, or many in a single call (idempotent — re-adding is a no-op).
|
|
98
|
+
unitpost.suppressions.create({"email": "bounced@example.com", "reason": "manual"})
|
|
99
|
+
unitpost.suppressions.create({"emails": ["a@example.com", "b@example.com"]})
|
|
100
|
+
|
|
101
|
+
# Look one up by id or email; list (optionally filter scope); un-suppress.
|
|
102
|
+
unitpost.suppressions.get("bounced@example.com")
|
|
103
|
+
for s in unitpost.suppressions.list_all():
|
|
104
|
+
print(s["email"], s["reason"])
|
|
105
|
+
unitpost.suppressions.delete("bounced@example.com")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`reason` accepts `manual`, `import`, or `api` — the engine-written `bounce` and
|
|
109
|
+
`complaint` reasons are read-only. Unitpost-wide (`platform`) entries are visible
|
|
110
|
+
but can't be removed via the API.
|
|
111
|
+
|
|
112
|
+
## Verifying webhooks
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from unitpost import verify_webhook
|
|
116
|
+
|
|
117
|
+
event = verify_webhook(
|
|
118
|
+
payload=raw_body, # the raw request body (str or bytes)
|
|
119
|
+
secret=os.environ["UNITPOST_WEBHOOK_SECRET"],
|
|
120
|
+
headers=request.headers,
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Or from the client (Resend-style alias):
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
event = client.webhooks.verify(payload=raw_body, secret=secret, headers=request.headers)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Idempotency
|
|
131
|
+
|
|
132
|
+
`emails.send` and `emails.batch` are safe to retry by default — if you don't pass
|
|
133
|
+
`idempotency_key`, the SDK generates one so an automatic retry can't double-send.
|
|
134
|
+
Pass your own to dedupe across separate calls:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
unitpost.email.send({...}, idempotency_key="...")
|
|
138
|
+
unitpost.email.batch([...], idempotency_key="...")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
> The server dedupes on `(workspace, idempotency key)` for 24h and replays the
|
|
142
|
+
> original result instead of re-sending (the email id for a send, the full list
|
|
143
|
+
> envelope for a batch).
|
|
144
|
+
|
|
145
|
+
## Retries
|
|
146
|
+
|
|
147
|
+
Transient failures are retried automatically: `429`, `5xx`, and transport errors
|
|
148
|
+
(timeouts / network) retry up to `max_retries` times (default `2`). A `429`/`503`
|
|
149
|
+
`Retry-After` is honored; otherwise exponential backoff with full jitter is used.
|
|
150
|
+
Client errors (`401`/`403`/`404`/`409`/`422`) are never retried. Only requests
|
|
151
|
+
safe to replay are retried: `GET`s and writes carrying an idempotency key (which
|
|
152
|
+
`emails.send` and `emails.batch` always do); any other keyless write is never
|
|
153
|
+
auto-retried.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
# Defaults shown; set max_retries=0 to disable.
|
|
157
|
+
unitpost = Unitpost(max_retries=2, retry_base_delay=0.5, max_retry_delay=20.0)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Resources
|
|
161
|
+
|
|
162
|
+
| | |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| Docs + API reference | https://www.unitpost.com/docs |
|
|
165
|
+
| Product / SMTP / MCP guides | https://www.unitpost.com/guides |
|
|
166
|
+
| OpenAPI JSON | https://www.unitpost.com/api/v1/openapi.json |
|
|
167
|
+
| LLM index | https://www.unitpost.com/llms.txt |
|
|
168
|
+
| Dashboard / API keys | https://www.unitpost.com |
|
|
169
|
+
| Email components (`@unitpost/email`) | https://www.unitpost.com/components |
|
|
170
|
+
| Template gallery | https://www.unitpost.com/templates/gallery |
|
|
171
|
+
| Playground | https://www.unitpost.com/playground |
|
|
172
|
+
| Migration Assistant | https://www.unitpost.com/migration |
|
|
173
|
+
| MCP + Agent Skills | https://www.unitpost.com/guides#ai-mcp · https://www.unitpost.com/guides#ai-skills |
|
|
174
|
+
| Support | support@unitpost.com |
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
unitpost-0.2.0/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# unitpost
|
|
2
|
+
|
|
3
|
+
Official [Unitpost](https://www.unitpost.com) SDK for Python. Send email and manage
|
|
4
|
+
contacts, segments, campaigns, templates, domains, suppressions, and webhooks.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install unitpost
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Requires Python 3.8+.
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from unitpost import Unitpost
|
|
18
|
+
|
|
19
|
+
unitpost = Unitpost() # reads UNITPOST_API_KEY (or pass Unitpost("up_..."))
|
|
20
|
+
|
|
21
|
+
result = unitpost.email.send({
|
|
22
|
+
"from": "Acme <you@yourdomain.com>", # bare address works too
|
|
23
|
+
"to": "customer@example.com",
|
|
24
|
+
"subject": "Hello",
|
|
25
|
+
"html": "<p>It works!</p>",
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if result.error:
|
|
29
|
+
print(result.error.code, result.error.message)
|
|
30
|
+
else:
|
|
31
|
+
print("sent", result.data["id"])
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Every method returns a `Result` with `.data` and `.error`. An API-level failure
|
|
35
|
+
populates `.error` (a `UnitpostError`) instead of raising.
|
|
36
|
+
|
|
37
|
+
## Authentication
|
|
38
|
+
|
|
39
|
+
Pass the key to `Unitpost(...)`, or set `UNITPOST_API_KEY`. Create a key at
|
|
40
|
+
[unitpost.com → Settings → API keys](https://www.unitpost.com).
|
|
41
|
+
|
|
42
|
+
## Client surface
|
|
43
|
+
|
|
44
|
+
| Accessor | Covers |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `emails` | Send, batch, list, get, update, stats, inbound (`received_*`) |
|
|
47
|
+
| `contacts` | CRUD, import jobs |
|
|
48
|
+
| `contact_fields` | Custom field definitions |
|
|
49
|
+
| `segments` | Segments + membership |
|
|
50
|
+
| `topics` | Topics + contact topic prefs |
|
|
51
|
+
| `campaigns` | Draft, schedule, send, pause/resume |
|
|
52
|
+
| `templates` | Template CRUD |
|
|
53
|
+
| `brand_kits` | Brand kit |
|
|
54
|
+
| `domains` | Domains + verify |
|
|
55
|
+
| `webhooks` | Endpoints + `verify()` |
|
|
56
|
+
| `api_keys` | API key management |
|
|
57
|
+
| `suppressions` | Suppression list |
|
|
58
|
+
| `usage` | Billing-period usage snapshot |
|
|
59
|
+
|
|
60
|
+
## Pagination
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
for contact in unitpost.contacts.list_all():
|
|
64
|
+
print(contact["email"])
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Suppressions
|
|
68
|
+
|
|
69
|
+
The suppression list holds addresses excluded from every send — single, batch,
|
|
70
|
+
and campaign, including cc/bcc — case-insensitively. Hard bounces and spam
|
|
71
|
+
complaints are added automatically; you can also add your own:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# Suppress one address, or many in a single call (idempotent — re-adding is a no-op).
|
|
75
|
+
unitpost.suppressions.create({"email": "bounced@example.com", "reason": "manual"})
|
|
76
|
+
unitpost.suppressions.create({"emails": ["a@example.com", "b@example.com"]})
|
|
77
|
+
|
|
78
|
+
# Look one up by id or email; list (optionally filter scope); un-suppress.
|
|
79
|
+
unitpost.suppressions.get("bounced@example.com")
|
|
80
|
+
for s in unitpost.suppressions.list_all():
|
|
81
|
+
print(s["email"], s["reason"])
|
|
82
|
+
unitpost.suppressions.delete("bounced@example.com")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`reason` accepts `manual`, `import`, or `api` — the engine-written `bounce` and
|
|
86
|
+
`complaint` reasons are read-only. Unitpost-wide (`platform`) entries are visible
|
|
87
|
+
but can't be removed via the API.
|
|
88
|
+
|
|
89
|
+
## Verifying webhooks
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from unitpost import verify_webhook
|
|
93
|
+
|
|
94
|
+
event = verify_webhook(
|
|
95
|
+
payload=raw_body, # the raw request body (str or bytes)
|
|
96
|
+
secret=os.environ["UNITPOST_WEBHOOK_SECRET"],
|
|
97
|
+
headers=request.headers,
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or from the client (Resend-style alias):
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
event = client.webhooks.verify(payload=raw_body, secret=secret, headers=request.headers)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Idempotency
|
|
108
|
+
|
|
109
|
+
`emails.send` and `emails.batch` are safe to retry by default — if you don't pass
|
|
110
|
+
`idempotency_key`, the SDK generates one so an automatic retry can't double-send.
|
|
111
|
+
Pass your own to dedupe across separate calls:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
unitpost.email.send({...}, idempotency_key="...")
|
|
115
|
+
unitpost.email.batch([...], idempotency_key="...")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> The server dedupes on `(workspace, idempotency key)` for 24h and replays the
|
|
119
|
+
> original result instead of re-sending (the email id for a send, the full list
|
|
120
|
+
> envelope for a batch).
|
|
121
|
+
|
|
122
|
+
## Retries
|
|
123
|
+
|
|
124
|
+
Transient failures are retried automatically: `429`, `5xx`, and transport errors
|
|
125
|
+
(timeouts / network) retry up to `max_retries` times (default `2`). A `429`/`503`
|
|
126
|
+
`Retry-After` is honored; otherwise exponential backoff with full jitter is used.
|
|
127
|
+
Client errors (`401`/`403`/`404`/`409`/`422`) are never retried. Only requests
|
|
128
|
+
safe to replay are retried: `GET`s and writes carrying an idempotency key (which
|
|
129
|
+
`emails.send` and `emails.batch` always do); any other keyless write is never
|
|
130
|
+
auto-retried.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
# Defaults shown; set max_retries=0 to disable.
|
|
134
|
+
unitpost = Unitpost(max_retries=2, retry_base_delay=0.5, max_retry_delay=20.0)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Resources
|
|
138
|
+
|
|
139
|
+
| | |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| Docs + API reference | https://www.unitpost.com/docs |
|
|
142
|
+
| Product / SMTP / MCP guides | https://www.unitpost.com/guides |
|
|
143
|
+
| OpenAPI JSON | https://www.unitpost.com/api/v1/openapi.json |
|
|
144
|
+
| LLM index | https://www.unitpost.com/llms.txt |
|
|
145
|
+
| Dashboard / API keys | https://www.unitpost.com |
|
|
146
|
+
| Email components (`@unitpost/email`) | https://www.unitpost.com/components |
|
|
147
|
+
| Template gallery | https://www.unitpost.com/templates/gallery |
|
|
148
|
+
| Playground | https://www.unitpost.com/playground |
|
|
149
|
+
| Migration Assistant | https://www.unitpost.com/migration |
|
|
150
|
+
| MCP + Agent Skills | https://www.unitpost.com/guides#ai-mcp · https://www.unitpost.com/guides#ai-skills |
|
|
151
|
+
| Support | support@unitpost.com |
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unitpost/sdk-python",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Workspace shim for the Python SDK (published to PyPI as `unitpost`). Lets Turborepo run its lint/test tasks; the real package lives under src/unitpost.",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "command -v python3 >/dev/null 2>&1 && python3 -c 'import httpx' >/dev/null 2>&1 && python3 -m pytest -q || echo 'skip: python/httpx not available (run in the dedicated sdks CI job)'",
|
|
8
|
+
"typecheck": "python3 -m mypy src/unitpost || true",
|
|
9
|
+
"build": "command -v python3 >/dev/null 2>&1 && python3 -c 'import build' >/dev/null 2>&1 && python3 -m build || echo 'skip: python build backend not available (the real sdist/wheel is built in publish-sdk-python.yml)'"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "unitpost"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Official Unitpost SDK for Python — send email and manage contacts, segments, campaigns, templates, brand kits, domains, suppressions, webhooks, and usage."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Unitpost" }]
|
|
13
|
+
keywords = ["unitpost", "email", "transactional-email", "sdk", "api"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"httpx>=0.28.1",
|
|
21
|
+
"typing_extensions>=4.16.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://www.unitpost.com"
|
|
26
|
+
Documentation = "https://www.unitpost.com/docs"
|
|
27
|
+
Repository = "https://github.com/unitpostcom/unitpost"
|
|
28
|
+
Issues = "https://github.com/unitpostcom/unitpost/issues"
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = ["pytest>=9.1.1", "respx>=0.23.1", "mypy>=2.3.0"]
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/unitpost"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
testpaths = ["tests"]
|
|
38
|
+
|
|
39
|
+
[tool.mypy]
|
|
40
|
+
python_version = "3.8"
|
|
41
|
+
strict = false
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""unitpost — Official Unitpost SDK for Python."""
|
|
2
|
+
|
|
3
|
+
from ._client import Unitpost
|
|
4
|
+
from ._http import Result, UnitpostError, SDK_VERSION
|
|
5
|
+
from ._webhooks import verify_webhook, WebhookVerificationError
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Unitpost",
|
|
9
|
+
"Result",
|
|
10
|
+
"UnitpostError",
|
|
11
|
+
"WebhookVerificationError",
|
|
12
|
+
"verify_webhook",
|
|
13
|
+
"SDK_VERSION",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
__version__ = SDK_VERSION
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""The Unitpost client. Construct once, reuse everywhere.
|
|
2
|
+
|
|
3
|
+
from unitpost import Unitpost
|
|
4
|
+
|
|
5
|
+
unitpost = Unitpost() # reads UNITPOST_API_KEY
|
|
6
|
+
result = unitpost.email.send({
|
|
7
|
+
"from": "Acme <you@yourdomain.com>",
|
|
8
|
+
"to": "customer@example.com",
|
|
9
|
+
"subject": "Hello",
|
|
10
|
+
"html": "<p>It works!</p>",
|
|
11
|
+
})
|
|
12
|
+
if result.error:
|
|
13
|
+
print(result.error.code, result.error.message)
|
|
14
|
+
else:
|
|
15
|
+
print("sent", result.data["id"])
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Callable, Optional
|
|
21
|
+
|
|
22
|
+
import httpx
|
|
23
|
+
|
|
24
|
+
from ._http import HttpClient
|
|
25
|
+
from .resources import (
|
|
26
|
+
ApiKeys,
|
|
27
|
+
BrandKits,
|
|
28
|
+
ContactFields,
|
|
29
|
+
Contacts,
|
|
30
|
+
Email,
|
|
31
|
+
Segments,
|
|
32
|
+
Suppressions,
|
|
33
|
+
Usage,
|
|
34
|
+
Webhooks,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Unitpost:
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
api_key: Optional[str] = None,
|
|
42
|
+
*,
|
|
43
|
+
base_url: Optional[str] = None,
|
|
44
|
+
timeout: float = 30.0,
|
|
45
|
+
headers: Optional[dict] = None,
|
|
46
|
+
transport: Optional[httpx.BaseTransport] = None,
|
|
47
|
+
max_retries: int = 2,
|
|
48
|
+
retry_base_delay: float = 0.5,
|
|
49
|
+
max_retry_delay: float = 20.0,
|
|
50
|
+
sleep: Optional[Callable[[float], None]] = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
http = HttpClient(
|
|
53
|
+
api_key,
|
|
54
|
+
base_url=base_url,
|
|
55
|
+
timeout=timeout,
|
|
56
|
+
headers=headers,
|
|
57
|
+
transport=transport,
|
|
58
|
+
max_retries=max_retries,
|
|
59
|
+
retry_base_delay=retry_base_delay,
|
|
60
|
+
max_retry_delay=max_retry_delay,
|
|
61
|
+
sleep=sleep,
|
|
62
|
+
)
|
|
63
|
+
self._http = http
|
|
64
|
+
self.email = Email(http)
|
|
65
|
+
self.contacts = Contacts(http)
|
|
66
|
+
self.contact_fields = ContactFields(http)
|
|
67
|
+
self.segments = Segments(http)
|
|
68
|
+
self.brand_kits = BrandKits(http)
|
|
69
|
+
self.webhooks = Webhooks(http)
|
|
70
|
+
self.api_keys = ApiKeys(http)
|
|
71
|
+
self.suppressions = Suppressions(http)
|
|
72
|
+
# NOTE (pre-launch): `events` is intentionally absent while the
|
|
73
|
+
# custom-events surface is behind the launch gate.
|
|
74
|
+
self.usage = Usage(http)
|
|
75
|
+
|
|
76
|
+
def close(self) -> None:
|
|
77
|
+
self._http.close()
|
|
78
|
+
|
|
79
|
+
def __enter__(self) -> "Unitpost":
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def __exit__(self, *exc: object) -> None:
|
|
83
|
+
self.close()
|