broadcast-python 0.2.0__tar.gz → 0.4.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.
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/CHANGELOG.md +23 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/PKG-INFO +33 -2
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/README.md +32 -1
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/pyproject.toml +1 -1
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/client.py +2 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/migration.py +2 -1
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/subscribers.py +8 -3
- broadcast_python-0.4.0/src/broadcast_python/resources/topics.py +29 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/version.py +1 -1
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/webhook.py +1 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/.gitignore +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/LICENSE +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/__init__.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/configuration.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/connection.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/errors.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/py.typed +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/__init__.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/autopilots.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/base.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/broadcasts.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/channel_design.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/discovery.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/email_servers.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/global_suppressions.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/opt_in_forms.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/segments.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/sequences.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/suppressions.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/templates.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/transactionals.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/users.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/webhook_endpoints.py +0 -0
- {broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/response.py +0 -0
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.4.0] - 2026-10-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `client.topics` for `/api/v1/topics` (subscriber topics): `list`, `get`,
|
|
12
|
+
`create`, `update`, `delete`. Topics use the subscriber permissions.
|
|
13
|
+
Broadcasts and sequences accept `topic_id`.
|
|
14
|
+
- `subscribers.update(email, custom_data_mode="merge", ...)`: change only the
|
|
15
|
+
custom_data keys sent; `None` deletes a key. The default stays replace.
|
|
16
|
+
- `subscriber.preferences_updated` webhook event type, in `SUBSCRIBER_EVENTS`
|
|
17
|
+
and `EVENT_TYPES` (now 35).
|
|
18
|
+
|
|
19
|
+
## [0.3.0] - 2026-10-03
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `client.migration.unsubscribed_emails()` for
|
|
24
|
+
`GET /api/migration/v1/unsubscribed_emails`, the channel's own suppression
|
|
25
|
+
list; `each_record("unsubscribed_emails")` pages it. `suppressions` returns
|
|
26
|
+
only the global suppression list, so an export needs both.
|
|
27
|
+
|
|
5
28
|
## [0.2.0] - 2026-09-25
|
|
6
29
|
|
|
7
30
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: broadcast-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python client for the Broadcast email platform. Subscribers, sequences, broadcasts, segments, templates, autopilot, webhooks, and transactional email.
|
|
5
5
|
Project-URL: Homepage, https://sendbroadcast.net
|
|
6
6
|
Project-URL: Repository, https://github.com/send-broadcast/broadcast-python
|
|
@@ -246,6 +246,32 @@ client.opt_in_forms.duplicate(id, label="Copy")
|
|
|
246
246
|
|
|
247
247
|
Reading a segment recounts its members server-side, so `segments.get` is not free.
|
|
248
248
|
|
|
249
|
+
### Topics
|
|
250
|
+
|
|
251
|
+
A topic is one kind of email subscribers opt in to or out of (webinars, offers).
|
|
252
|
+
Its value lives in a top-level `custom_data` key (`True`, `False`, or no value)
|
|
253
|
+
or in a tag. `unset_receives` decides, at send time, whether a subscriber with no
|
|
254
|
+
value receives it; Broadcast never writes a default. Topics use the subscriber
|
|
255
|
+
permissions.
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
topic = client.topics.create(name="Webinars", custom_data_key="sub_webinars")
|
|
259
|
+
client.topics.create(name="News", storage="tag", tag_name="news")
|
|
260
|
+
client.topics.list()
|
|
261
|
+
client.topics.update(topic["id"], unset_receives=False)
|
|
262
|
+
client.topics.delete(topic["id"]) # 422 while a broadcast or sequence uses it
|
|
263
|
+
|
|
264
|
+
# Send to a topic: (segments) AND topic
|
|
265
|
+
client.broadcasts.create(subject="Webinar", body="...", segment_ids=[2], topic_id=topic["id"])
|
|
266
|
+
|
|
267
|
+
# Change only the keys you send (None deletes a key); the default replaces custom_data
|
|
268
|
+
client.subscribers.update("jane@example.com", custom_data={"sub_webinars": False}, custom_data_mode="merge")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The subscriber JSON has `topics`: each topic's stored value (`True`, `False`, or
|
|
272
|
+
`None`). Topic changes fire `subscriber.preferences_updated` as well as
|
|
273
|
+
`subscriber.updated`.
|
|
274
|
+
|
|
249
275
|
### Channel design (brand kit)
|
|
250
276
|
|
|
251
277
|
```python
|
|
@@ -363,6 +389,11 @@ client.migration.manifest()
|
|
|
363
389
|
for sub in client.migration.each_record("subscribers"):
|
|
364
390
|
... # auto-pages; advances by the limit the server actually applied
|
|
365
391
|
|
|
392
|
+
# Suppressed addresses live in two lists; export both:
|
|
393
|
+
# suppressions = global suppression list, unsubscribed_emails = the channel's own
|
|
394
|
+
for row in client.migration.each_record("unsubscribed_emails"):
|
|
395
|
+
... # {"id", "email", "broadcast_channel_id", "created_at", "updated_at"}
|
|
396
|
+
|
|
366
397
|
data = client.migration.download_file_asset(id) # bytes
|
|
367
398
|
```
|
|
368
399
|
|
|
@@ -408,7 +439,7 @@ every rejection rather than distinguishing them.
|
|
|
408
439
|
Pass the **raw** request body. Re-serialising a parsed dict changes the bytes
|
|
409
440
|
and verification will fail.
|
|
410
441
|
|
|
411
|
-
`broadcast_python.EVENT_TYPES` lists all
|
|
442
|
+
`broadcast_python.EVENT_TYPES` lists all 35 event names. `SUBSCRIBER_EVENTS`
|
|
412
443
|
includes `subscribers.purged` and `subscribers.purge_failed`: a purge of the
|
|
413
444
|
whole list sends one of these instead of a `subscriber.deleted` per subscriber.
|
|
414
445
|
|
|
@@ -222,6 +222,32 @@ client.opt_in_forms.duplicate(id, label="Copy")
|
|
|
222
222
|
|
|
223
223
|
Reading a segment recounts its members server-side, so `segments.get` is not free.
|
|
224
224
|
|
|
225
|
+
### Topics
|
|
226
|
+
|
|
227
|
+
A topic is one kind of email subscribers opt in to or out of (webinars, offers).
|
|
228
|
+
Its value lives in a top-level `custom_data` key (`True`, `False`, or no value)
|
|
229
|
+
or in a tag. `unset_receives` decides, at send time, whether a subscriber with no
|
|
230
|
+
value receives it; Broadcast never writes a default. Topics use the subscriber
|
|
231
|
+
permissions.
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
topic = client.topics.create(name="Webinars", custom_data_key="sub_webinars")
|
|
235
|
+
client.topics.create(name="News", storage="tag", tag_name="news")
|
|
236
|
+
client.topics.list()
|
|
237
|
+
client.topics.update(topic["id"], unset_receives=False)
|
|
238
|
+
client.topics.delete(topic["id"]) # 422 while a broadcast or sequence uses it
|
|
239
|
+
|
|
240
|
+
# Send to a topic: (segments) AND topic
|
|
241
|
+
client.broadcasts.create(subject="Webinar", body="...", segment_ids=[2], topic_id=topic["id"])
|
|
242
|
+
|
|
243
|
+
# Change only the keys you send (None deletes a key); the default replaces custom_data
|
|
244
|
+
client.subscribers.update("jane@example.com", custom_data={"sub_webinars": False}, custom_data_mode="merge")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The subscriber JSON has `topics`: each topic's stored value (`True`, `False`, or
|
|
248
|
+
`None`). Topic changes fire `subscriber.preferences_updated` as well as
|
|
249
|
+
`subscriber.updated`.
|
|
250
|
+
|
|
225
251
|
### Channel design (brand kit)
|
|
226
252
|
|
|
227
253
|
```python
|
|
@@ -339,6 +365,11 @@ client.migration.manifest()
|
|
|
339
365
|
for sub in client.migration.each_record("subscribers"):
|
|
340
366
|
... # auto-pages; advances by the limit the server actually applied
|
|
341
367
|
|
|
368
|
+
# Suppressed addresses live in two lists; export both:
|
|
369
|
+
# suppressions = global suppression list, unsubscribed_emails = the channel's own
|
|
370
|
+
for row in client.migration.each_record("unsubscribed_emails"):
|
|
371
|
+
... # {"id", "email", "broadcast_channel_id", "created_at", "updated_at"}
|
|
372
|
+
|
|
342
373
|
data = client.migration.download_file_asset(id) # bytes
|
|
343
374
|
```
|
|
344
375
|
|
|
@@ -384,7 +415,7 @@ every rejection rather than distinguishing them.
|
|
|
384
415
|
Pass the **raw** request body. Re-serialising a parsed dict changes the bytes
|
|
385
416
|
and verification will fail.
|
|
386
417
|
|
|
387
|
-
`broadcast_python.EVENT_TYPES` lists all
|
|
418
|
+
`broadcast_python.EVENT_TYPES` lists all 35 event names. `SUBSCRIBER_EVENTS`
|
|
388
419
|
includes `subscribers.purged` and `subscribers.purge_failed`: a purge of the
|
|
389
420
|
whole list sends one of these instead of a `subscriber.deleted` per subscriber.
|
|
390
421
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "broadcast-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Python client for the Broadcast email platform. Subscribers, sequences, broadcasts, segments, templates, autopilot, webhooks, and transactional email."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -16,6 +16,7 @@ from .resources.sequences import Sequences
|
|
|
16
16
|
from .resources.subscribers import Subscribers
|
|
17
17
|
from .resources.suppressions import Suppressions
|
|
18
18
|
from .resources.templates import Templates
|
|
19
|
+
from .resources.topics import Topics
|
|
19
20
|
from .resources.transactionals import Transactionals
|
|
20
21
|
from .resources.users import Users
|
|
21
22
|
from .resources.webhook_endpoints import WebhookEndpoints
|
|
@@ -40,6 +41,7 @@ class Broadcast:
|
|
|
40
41
|
self.sequences = Sequences(self)
|
|
41
42
|
self.broadcasts = Broadcasts(self)
|
|
42
43
|
self.segments = Segments(self)
|
|
44
|
+
self.topics = Topics(self)
|
|
43
45
|
self.templates = Templates(self)
|
|
44
46
|
#: The channel's brand kit (Settings -> Design). Read-only.
|
|
45
47
|
self.channel_design = ChannelDesign(self)
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/migration.py
RENAMED
|
@@ -35,6 +35,7 @@ COLLECTIONS = (
|
|
|
35
35
|
"webhook_endpoints",
|
|
36
36
|
"tokens",
|
|
37
37
|
"suppressions",
|
|
38
|
+
"unsubscribed_emails",
|
|
38
39
|
"tags",
|
|
39
40
|
"users",
|
|
40
41
|
"link_redirects",
|
|
@@ -106,7 +107,7 @@ def _make_collection_method(name: str):
|
|
|
106
107
|
return method
|
|
107
108
|
|
|
108
109
|
|
|
109
|
-
# Generated rather than hand-written:
|
|
110
|
+
# Generated rather than hand-written: 19 near-identical methods invite the kind
|
|
110
111
|
# of copy-paste drift this whole SDK family exists to prevent. Declared in
|
|
111
112
|
# .api-coverage.yml so the coverage report still counts them.
|
|
112
113
|
for _collection in COLLECTIONS:
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/subscribers.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Dict, List
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
2
|
|
|
3
3
|
from .base import BaseResource
|
|
4
4
|
|
|
@@ -46,8 +46,13 @@ class Subscribers(BaseResource):
|
|
|
46
46
|
|
|
47
47
|
return self._post("/api/v1/subscribers.json", payload)
|
|
48
48
|
|
|
49
|
-
def update(self, email: str, **attrs: Any) -> Any:
|
|
50
|
-
|
|
49
|
+
def update(self, email: str, custom_data_mode: Optional[str] = None, **attrs: Any) -> Any:
|
|
50
|
+
"""custom_data_mode: "replace" (the API default) stores custom_data as sent;
|
|
51
|
+
"merge" changes only the keys sent, and a None value deletes that key."""
|
|
52
|
+
payload: Dict[str, Any] = {"email": email, "subscriber": attrs}
|
|
53
|
+
if custom_data_mode:
|
|
54
|
+
payload["custom_data_mode"] = custom_data_mode
|
|
55
|
+
return self._patch("/api/v1/subscribers.json", payload)
|
|
51
56
|
|
|
52
57
|
def add_tags(self, email: str, tags: List[str]) -> Any:
|
|
53
58
|
return self._post("/api/v1/subscribers/add_tag.json", {"email": email, "tags": tags})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Any, Union
|
|
2
|
+
|
|
3
|
+
from .base import BaseResource
|
|
4
|
+
|
|
5
|
+
Id = Union[str, int]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Topics(BaseResource):
|
|
9
|
+
"""Subscriber topics: kinds of email subscribers opt in to or out of.
|
|
10
|
+
|
|
11
|
+
A topic reads a top-level custom_data key (true, false, or no value) or a
|
|
12
|
+
tag. Topics use the token's subscriber permissions.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def list(self, **params: Any) -> Any:
|
|
16
|
+
return self._get("/api/v1/topics.json", params)
|
|
17
|
+
|
|
18
|
+
def get(self, id: Id) -> Any: # noqa: A002
|
|
19
|
+
return self._get("/api/v1/topics/{}.json".format(id), {})
|
|
20
|
+
|
|
21
|
+
def create(self, **attrs: Any) -> Any:
|
|
22
|
+
return self._post("/api/v1/topics", {"topic": attrs})
|
|
23
|
+
|
|
24
|
+
def update(self, id: Id, **attrs: Any) -> Any: # noqa: A002
|
|
25
|
+
return self._patch("/api/v1/topics/{}".format(id), {"topic": attrs})
|
|
26
|
+
|
|
27
|
+
def delete(self, id: Id) -> Any: # noqa: A002
|
|
28
|
+
"""Refused (422) while a broadcast or sequence uses the topic."""
|
|
29
|
+
return self._delete("/api/v1/topics/{}".format(id))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/__init__.py
RENAMED
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/autopilots.py
RENAMED
|
File without changes
|
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/broadcasts.py
RENAMED
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/channel_design.py
RENAMED
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/discovery.py
RENAMED
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/email_servers.py
RENAMED
|
File without changes
|
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/opt_in_forms.py
RENAMED
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/segments.py
RENAMED
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/sequences.py
RENAMED
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/suppressions.py
RENAMED
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/templates.py
RENAMED
|
File without changes
|
{broadcast_python-0.2.0 → broadcast_python-0.4.0}/src/broadcast_python/resources/transactionals.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|