broadcast-python 0.3.0__py3-none-any.whl → 0.5.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.
- broadcast_python/__init__.py +2 -0
- broadcast_python/client.py +2 -0
- broadcast_python/resources/autopilots.py +2 -1
- broadcast_python/resources/opt_in_forms.py +9 -0
- broadcast_python/resources/subscribers.py +13 -4
- broadcast_python/resources/topics.py +29 -0
- broadcast_python/version.py +1 -1
- broadcast_python/webhook.py +3 -0
- {broadcast_python-0.3.0.dist-info → broadcast_python-0.5.0.dist-info}/METADATA +36 -2
- {broadcast_python-0.3.0.dist-info → broadcast_python-0.5.0.dist-info}/RECORD +12 -11
- {broadcast_python-0.3.0.dist-info → broadcast_python-0.5.0.dist-info}/WHEEL +0 -0
- {broadcast_python-0.3.0.dist-info → broadcast_python-0.5.0.dist-info}/licenses/LICENSE +0 -0
broadcast_python/__init__.py
CHANGED
|
@@ -28,6 +28,7 @@ from .errors import (
|
|
|
28
28
|
)
|
|
29
29
|
from .resources.email_servers import REDACTED_FIELDS
|
|
30
30
|
from .resources.migration import COLLECTIONS
|
|
31
|
+
from .resources.opt_in_forms import TRIGGER_FREQUENCIES
|
|
31
32
|
from .resources.transactionals import MAX_IDEMPOTENCY_KEY_LENGTH
|
|
32
33
|
from .response import RateLimit, Response, Warning_
|
|
33
34
|
from .version import VERSION
|
|
@@ -42,6 +43,7 @@ __all__ = [
|
|
|
42
43
|
"EVENT_TYPES",
|
|
43
44
|
"MAX_IDEMPOTENCY_KEY_LENGTH",
|
|
44
45
|
"REDACTED_FIELDS",
|
|
46
|
+
"TRIGGER_FREQUENCIES",
|
|
45
47
|
"VERSION",
|
|
46
48
|
"WARNINGS_MODES",
|
|
47
49
|
"APIError",
|
broadcast_python/client.py
CHANGED
|
@@ -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)
|
|
@@ -36,7 +36,8 @@ class Autopilots(BaseResource):
|
|
|
36
36
|
write-only. Scheduling takes ``schedule_frequency`` (daily, weekly,
|
|
37
37
|
biweekly, monthly), ``schedule_day_of_week``, ``schedule_day_of_month``,
|
|
38
38
|
``schedule_time``, ``schedule_timezone``. ``segment_ids`` restricts the
|
|
39
|
-
newsletter's audience
|
|
39
|
+
newsletter's audience; ``excluded_segment_ids`` lists segments its
|
|
40
|
+
drafts never reach.
|
|
40
41
|
"""
|
|
41
42
|
return self._post("/api/v1/autopilots", {"autopilot": attrs})
|
|
42
43
|
|
|
@@ -5,6 +5,12 @@ from .base import BaseResource, compact
|
|
|
5
5
|
|
|
6
6
|
Id = Union[str, int]
|
|
7
7
|
|
|
8
|
+
#: The words ``trigger_settings["frequency"]`` accepts: how often the same
|
|
9
|
+
#: visitor sees a popup. ``always`` and ``every_visit`` mean the same. The server
|
|
10
|
+
#: refuses any other word with 422 (:class:`ValidationError`); the client does
|
|
11
|
+
#: not check, so an older server is never refused a word it would take.
|
|
12
|
+
TRIGGER_FREQUENCIES = ("always", "every_visit", "once_per_session", "once_per_day", "once_per_week", "once")
|
|
13
|
+
|
|
8
14
|
|
|
9
15
|
class OptInForms(BaseResource):
|
|
10
16
|
def list(self, **params: Any) -> Any:
|
|
@@ -23,6 +29,9 @@ class OptInForms(BaseResource):
|
|
|
23
29
|
Nested settings dicts (``theme_settings``, ``automation_settings``,
|
|
24
30
|
``security_settings``, ``trigger_settings``, ``widget_settings``) and
|
|
25
31
|
the block arrays are passed through verbatim.
|
|
32
|
+
``trigger_settings["frequency"]`` must be one of
|
|
33
|
+
:data:`TRIGGER_FREQUENCIES`; any other word raises
|
|
34
|
+
:class:`ValidationError` (422).
|
|
26
35
|
"""
|
|
27
36
|
return self._post("/api/v1/opt_in_forms", {"opt_in_form": attrs})
|
|
28
37
|
|
|
@@ -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
|
|
|
@@ -33,7 +33,11 @@ class Subscribers(BaseResource):
|
|
|
33
33
|
timestamp when migrating an already-confirmed list off another provider.
|
|
34
34
|
It is ignored (with a warning) on update.
|
|
35
35
|
|
|
36
|
-
``unsubscribed_at``
|
|
36
|
+
``unsubscribed_at`` (any token, create only, ISO 8601) stores the
|
|
37
|
+
subscriber as unsubscribed in the same request, for migrating a list
|
|
38
|
+
with its opt-outs intact: no confirmation email, 422 if it is in the
|
|
39
|
+
future or sent with ``is_active=True``. To unsubscribe an existing
|
|
40
|
+
subscriber, use :meth:`unsubscribe`.
|
|
37
41
|
"""
|
|
38
42
|
double_opt_in = attrs.pop("double_opt_in", None)
|
|
39
43
|
confirmation_template_id = attrs.pop("confirmation_template_id", None)
|
|
@@ -46,8 +50,13 @@ class Subscribers(BaseResource):
|
|
|
46
50
|
|
|
47
51
|
return self._post("/api/v1/subscribers.json", payload)
|
|
48
52
|
|
|
49
|
-
def update(self, email: str, **attrs: Any) -> Any:
|
|
50
|
-
|
|
53
|
+
def update(self, email: str, custom_data_mode: Optional[str] = None, **attrs: Any) -> Any:
|
|
54
|
+
"""custom_data_mode: "replace" (the API default) stores custom_data as sent;
|
|
55
|
+
"merge" changes only the keys sent, and a None value deletes that key."""
|
|
56
|
+
payload: Dict[str, Any] = {"email": email, "subscriber": attrs}
|
|
57
|
+
if custom_data_mode:
|
|
58
|
+
payload["custom_data_mode"] = custom_data_mode
|
|
59
|
+
return self._patch("/api/v1/subscribers.json", payload)
|
|
51
60
|
|
|
52
61
|
def add_tags(self, email: str, tags: List[str]) -> Any:
|
|
53
62
|
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))
|
broadcast_python/version.py
CHANGED
broadcast_python/webhook.py
CHANGED
|
@@ -15,6 +15,7 @@ EMAIL_EVENTS = (
|
|
|
15
15
|
"email.sent",
|
|
16
16
|
"email.delivered",
|
|
17
17
|
"email.delivery_delayed",
|
|
18
|
+
"email.send_delayed",
|
|
18
19
|
"email.complained",
|
|
19
20
|
"email.bounced",
|
|
20
21
|
"email.opened",
|
|
@@ -25,6 +26,7 @@ EMAIL_EVENTS = (
|
|
|
25
26
|
SUBSCRIBER_EVENTS = (
|
|
26
27
|
"subscriber.created",
|
|
27
28
|
"subscriber.updated",
|
|
29
|
+
"subscriber.preferences_updated",
|
|
28
30
|
"subscriber.deleted",
|
|
29
31
|
"subscriber.subscribed",
|
|
30
32
|
"subscriber.unsubscribed",
|
|
@@ -44,6 +46,7 @@ BROADCAST_EVENTS = (
|
|
|
44
46
|
"broadcast.partial_failure",
|
|
45
47
|
"broadcast.aborted",
|
|
46
48
|
"broadcast.paused",
|
|
49
|
+
"broadcast.batch_completed",
|
|
47
50
|
)
|
|
48
51
|
|
|
49
52
|
SEQUENCE_EVENTS = (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: broadcast-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -240,12 +240,46 @@ client.sequences.move_step(id, step_id, under_id)
|
|
|
240
240
|
client.segments.create(name="VIPs")
|
|
241
241
|
client.templates.create(label="Welcome", subject="Hi", body="...")
|
|
242
242
|
client.opt_in_forms.analytics(id, start_date=date(2026, 1, 1))
|
|
243
|
+
# How often the same visitor sees a popup: one of TRIGGER_FREQUENCIES, or ValidationError (422).
|
|
244
|
+
client.opt_in_forms.update(id, trigger_settings={"frequency": "once_per_day"})
|
|
243
245
|
client.opt_in_forms.create_variant(id, name="B", weight=50)
|
|
244
246
|
client.opt_in_forms.duplicate(id, label="Copy")
|
|
245
247
|
```
|
|
246
248
|
|
|
247
249
|
Reading a segment recounts its members server-side, so `segments.get` is not free.
|
|
248
250
|
|
|
251
|
+
### Topics
|
|
252
|
+
|
|
253
|
+
A topic is one kind of email subscribers opt in to or out of (webinars, offers).
|
|
254
|
+
Its value lives in a top-level `custom_data` key (`True`, `False`, or no value)
|
|
255
|
+
or in a tag. `unset_receives` decides, at send time, whether a subscriber with no
|
|
256
|
+
value receives it; Broadcast never writes a default. Topics use the subscriber
|
|
257
|
+
permissions.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
topic = client.topics.create(name="Webinars", custom_data_key="sub_webinars")
|
|
261
|
+
client.topics.create(name="News", storage="tag", tag_name="news")
|
|
262
|
+
client.topics.list()
|
|
263
|
+
client.topics.update(topic["id"], unset_receives=False)
|
|
264
|
+
client.topics.delete(topic["id"]) # 422 while a broadcast or sequence uses it
|
|
265
|
+
|
|
266
|
+
# Send to a topic: (segments) AND topic
|
|
267
|
+
client.broadcasts.create(subject="Webinar", body="...", segment_ids=[2], topic_id=topic["id"])
|
|
268
|
+
|
|
269
|
+
# Send to everyone except a segment (also on sequences and autopilots)
|
|
270
|
+
client.broadcasts.create(subject="Offer", body="...", excluded_segment_ids=[competitors["id"]])
|
|
271
|
+
|
|
272
|
+
# Migrate someone who already unsubscribed, in one request
|
|
273
|
+
client.subscribers.create(email="opted-out@example.com", unsubscribed_at="2025-03-14T09:30:00Z")
|
|
274
|
+
|
|
275
|
+
# Change only the keys you send (None deletes a key); the default replaces custom_data
|
|
276
|
+
client.subscribers.update("jane@example.com", custom_data={"sub_webinars": False}, custom_data_mode="merge")
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
The subscriber JSON has `topics`: each topic's stored value (`True`, `False`, or
|
|
280
|
+
`None`). Topic changes fire `subscriber.preferences_updated` as well as
|
|
281
|
+
`subscriber.updated`.
|
|
282
|
+
|
|
249
283
|
### Channel design (brand kit)
|
|
250
284
|
|
|
251
285
|
```python
|
|
@@ -413,7 +447,7 @@ every rejection rather than distinguishing them.
|
|
|
413
447
|
Pass the **raw** request body. Re-serialising a parsed dict changes the bytes
|
|
414
448
|
and verification will fail.
|
|
415
449
|
|
|
416
|
-
`broadcast_python.EVENT_TYPES` lists all
|
|
450
|
+
`broadcast_python.EVENT_TYPES` lists all 37 event names. `SUBSCRIBER_EVENTS`
|
|
417
451
|
includes `subscribers.purged` and `subscribers.purge_failed`: a purge of the
|
|
418
452
|
whole list sends one of these instead of a `subscriber.deleted` per subscriber.
|
|
419
453
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
broadcast_python/__init__.py,sha256=
|
|
2
|
-
broadcast_python/client.py,sha256=
|
|
1
|
+
broadcast_python/__init__.py,sha256=ZDjU3aAHHMONoEKnAPZpralqN1a3Z3Ddv73mkQWt4U0,1715
|
|
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=
|
|
9
|
-
broadcast_python/webhook.py,sha256=
|
|
8
|
+
broadcast_python/version.py,sha256=Zz7Z0-OMagmQ4tVooxvKzJdH4Lhpwhuk1QkOMHsYGHY,169
|
|
9
|
+
broadcast_python/webhook.py,sha256=2OtgY4IqKcf73hyUcCwZWpS_VURPjU2ZOgizl8Wz3Dg,3776
|
|
10
10
|
broadcast_python/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
broadcast_python/resources/autopilots.py,sha256=
|
|
11
|
+
broadcast_python/resources/autopilots.py,sha256=PVOetU4DwF5ISZKuvN1UYr5a6hnwiWJLt1AcAo6wMbc,3488
|
|
12
12
|
broadcast_python/resources/base.py,sha256=-_uG_bAy_sz2oGHe2GQ64zRcpef6WZdMe4oHGJYFqEw,1632
|
|
13
13
|
broadcast_python/resources/broadcasts.py,sha256=VuYkKJ3d3mcaShXciMqAPHH-OT8px559VeXNltqkx3A,1954
|
|
14
14
|
broadcast_python/resources/channel_design.py,sha256=4xiJMGkiBjbCH35LQi_2DqrZV8n5mi6XXhzJf6sGpLM,970
|
|
@@ -16,16 +16,17 @@ broadcast_python/resources/discovery.py,sha256=B0BKq9hpRwd9gN4MWoPArz2dli4bQLZs9
|
|
|
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
18
|
broadcast_python/resources/migration.py,sha256=EvqUvn254Tr_mMVM7Ki3MZb0o0c8_PO4ZUo-lHnvBTA,4126
|
|
19
|
-
broadcast_python/resources/opt_in_forms.py,sha256=
|
|
19
|
+
broadcast_python/resources/opt_in_forms.py,sha256=271hVuzmS1Cda9cT0UZLkeuUaYH1kJZYw-NpUV_g1pE,3083
|
|
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=
|
|
22
|
+
broadcast_python/resources/subscribers.py,sha256=OMcdlfDMgs-gUXIYGLhw3IoRQbBlChFT6EcJOJ2FeVE,3786
|
|
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.
|
|
29
|
-
broadcast_python-0.
|
|
30
|
-
broadcast_python-0.
|
|
31
|
-
broadcast_python-0.
|
|
29
|
+
broadcast_python-0.5.0.dist-info/METADATA,sha256=KSQA5BbjGD95C8czXbEiACxNCXbPzvGWqIcH4XAo_SA,21319
|
|
30
|
+
broadcast_python-0.5.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
31
|
+
broadcast_python-0.5.0.dist-info/licenses/LICENSE,sha256=cnoPKJuOFIyR8mBe7kmEcUQelniJaujjGl21QWqzlKE,1067
|
|
32
|
+
broadcast_python-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|