broadcast-python 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

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.
@@ -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)
@@ -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: 18 near-identical methods invite the kind
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:
@@ -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
- return self._patch("/api/v1/subscribers.json", {"email": email, "subscriber": attrs})
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))
@@ -1,3 +1,3 @@
1
1
  # Kept in sync with pyproject.toml by tests/test_package.py — a User-Agent that
2
2
  # lies about its version misattributes server-side client analytics.
3
- VERSION = "0.2.0"
3
+ VERSION = "0.4.0"
@@ -25,6 +25,7 @@ EMAIL_EVENTS = (
25
25
  SUBSCRIBER_EVENTS = (
26
26
  "subscriber.created",
27
27
  "subscriber.updated",
28
+ "subscriber.preferences_updated",
28
29
  "subscriber.deleted",
29
30
  "subscriber.subscribed",
30
31
  "subscriber.unsubscribed",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: broadcast-python
3
- Version: 0.2.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 34 event names. `SUBSCRIBER_EVENTS`
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
 
@@ -1,12 +1,12 @@
1
1
  broadcast_python/__init__.py,sha256=_mzCFewxAEutE4fJ7hRPpaHuQwXpliHt5BNRE-J80TY,1632
2
- broadcast_python/client.py,sha256=9aS5Jhq06Um-nM0s9idUM2Cs_LkmJVG8THHqiHESbpA,5471
2
+ broadcast_python/client.py,sha256=Sd_8vA0lFCl9Da3luwC10k9cGaREyK-AwotFXE4kb4c,5543
3
3
  broadcast_python/configuration.py,sha256=kXheoYhwRJo6STIL9Wmhl_MOEdWfaV3CBETEh1SbEcI,3961
4
4
  broadcast_python/connection.py,sha256=cAXyfwJ1pdleLm7S9fr1FUk5nqkdOTdav6dGZGJ0GfU,14064
5
5
  broadcast_python/errors.py,sha256=NFJ69Oy_FtYZ3-BEq8Q1m5wLqcufRZLPDYuJlgBfGpc,1993
6
6
  broadcast_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  broadcast_python/response.py,sha256=fIBzxi4a2Sc7sNSOb2_t85Y3Opdet89uNbHM5CX3YXw,5511
8
- broadcast_python/version.py,sha256=f52uUZ9_ePggmFw6jNA8hgza42_PJo1M9gSQXdKeymI,169
9
- broadcast_python/webhook.py,sha256=eLSxUkZ75dqEMoKfjLiG6SAVSVmxa0TQlhPl9MwZKaM,3679
8
+ broadcast_python/version.py,sha256=4w8Ic2wUVGeOQ0Qa4FK6ErQDU1YfEIjcoIulx3E47j4,169
9
+ broadcast_python/webhook.py,sha256=QamE73PkdyC6zax90Jd8FSmfbBBdX8lcbD4rSp7qYhw,3717
10
10
  broadcast_python/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  broadcast_python/resources/autopilots.py,sha256=yDXs7lPi2tqJwPEi_e156jRFQIkVamsdvYGiHax_7HM,3416
12
12
  broadcast_python/resources/base.py,sha256=-_uG_bAy_sz2oGHe2GQ64zRcpef6WZdMe4oHGJYFqEw,1632
@@ -15,17 +15,18 @@ broadcast_python/resources/channel_design.py,sha256=4xiJMGkiBjbCH35LQi_2DqrZV8n5
15
15
  broadcast_python/resources/discovery.py,sha256=B0BKq9hpRwd9gN4MWoPArz2dli4bQLZs9T6IYB8edC4,1960
16
16
  broadcast_python/resources/email_servers.py,sha256=1_ZJEPwVjFi5rN6S_0n7WfFfmF5y3JDIcW0Ixak0Z24,2830
17
17
  broadcast_python/resources/global_suppressions.py,sha256=5Nghq0AfQYLPEL_IAc_tmwGfovA5jpLmdw8yUxg6HpA,1730
18
- broadcast_python/resources/migration.py,sha256=y-GqY4BWzdNnlc6TWu7wNmqcqzEWSroxva1KiMqAJQg,4099
18
+ broadcast_python/resources/migration.py,sha256=EvqUvn254Tr_mMVM7Ki3MZb0o0c8_PO4ZUo-lHnvBTA,4126
19
19
  broadcast_python/resources/opt_in_forms.py,sha256=ScoEKXejT27UfTL8iGxlsADO6g5Q-4xCpWOFtmM6q2E,2509
20
20
  broadcast_python/resources/segments.py,sha256=ZfUJxiWPhERWzkzuRGVu09uWqbwyUbdr3NsKOEeuW8o,857
21
21
  broadcast_python/resources/sequences.py,sha256=9IuKw13e_LYZMLZiBYrr0b-BPhAAAucdt8DUjQvbP80,2467
22
- broadcast_python/resources/subscribers.py,sha256=DtUnHs1rj3LF7KACQRQFB-ofgAHl1WTiLluL4W7xbfw,3168
22
+ broadcast_python/resources/subscribers.py,sha256=EZu1cuWaBRZoXK3HB31-N46KpdeCfMvvpyRcdZNB5Lo,3515
23
23
  broadcast_python/resources/suppressions.py,sha256=3IHMkWHgU3ArxcDeMSzPDFWL1Ngag2-m04XNll31C8c,2354
24
24
  broadcast_python/resources/templates.py,sha256=FP0KcP61KkfrRID1XeAQ8hRxxRyxBzt404nXmnBqXNY,1262
25
+ broadcast_python/resources/topics.py,sha256=jgspk04PgpxzGpehP9DO_AjKk361pgsbaYFR_BixLLs,980
25
26
  broadcast_python/resources/transactionals.py,sha256=iwe9aTHKpeEMtDxd_S2TVzdUVm5Phno-Vinrxu9bn6w,2940
26
27
  broadcast_python/resources/users.py,sha256=25gPmWLI1rqQnS9gCOi_2esUjdz_e0Zfi7_7Y2x91mg,4813
27
28
  broadcast_python/resources/webhook_endpoints.py,sha256=Llkry10QT3JavkmKok3IAayYKM1PD1-iI51YhW3RbpE,1180
28
- broadcast_python-0.2.0.dist-info/METADATA,sha256=lNr6zS4CcDh3Ws4Ts6_VnuCdo148GTkJeIAeE-V2NnQ,19364
29
- broadcast_python-0.2.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
30
- broadcast_python-0.2.0.dist-info/licenses/LICENSE,sha256=cnoPKJuOFIyR8mBe7kmEcUQelniJaujjGl21QWqzlKE,1067
31
- broadcast_python-0.2.0.dist-info/RECORD,,
29
+ broadcast_python-0.4.0.dist-info/METADATA,sha256=DoYhUeO0kqnOSMV1EFsED4vxbLdVC8ItBtMYAWLnqR4,20818
30
+ broadcast_python-0.4.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
31
+ broadcast_python-0.4.0.dist-info/licenses/LICENSE,sha256=cnoPKJuOFIyR8mBe7kmEcUQelniJaujjGl21QWqzlKE,1067
32
+ broadcast_python-0.4.0.dist-info/RECORD,,