broadcast-python 0.1.0__py3-none-any.whl → 0.2.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/client.py +18 -0
- broadcast_python/resources/base.py +3 -0
- broadcast_python/resources/channel_design.py +24 -0
- broadcast_python/resources/discovery.py +14 -0
- broadcast_python/resources/global_suppressions.py +40 -0
- broadcast_python/resources/suppressions.py +52 -0
- broadcast_python/resources/users.py +115 -0
- broadcast_python/version.py +1 -1
- broadcast_python/webhook.py +3 -0
- {broadcast_python-0.1.0.dist-info → broadcast_python-0.2.0.dist-info}/METADATA +54 -13
- {broadcast_python-0.1.0.dist-info → broadcast_python-0.2.0.dist-info}/RECORD +13 -9
- {broadcast_python-0.1.0.dist-info → broadcast_python-0.2.0.dist-info}/WHEEL +1 -1
- {broadcast_python-0.1.0.dist-info → broadcast_python-0.2.0.dist-info}/licenses/LICENSE +0 -0
broadcast_python/client.py
CHANGED
|
@@ -5,15 +5,19 @@ from .configuration import Configuration
|
|
|
5
5
|
from .connection import Connection
|
|
6
6
|
from .resources.autopilots import Autopilots
|
|
7
7
|
from .resources.broadcasts import Broadcasts
|
|
8
|
+
from .resources.channel_design import ChannelDesign
|
|
8
9
|
from .resources.discovery import Discovery
|
|
9
10
|
from .resources.email_servers import EmailServers
|
|
11
|
+
from .resources.global_suppressions import GlobalSuppressions
|
|
10
12
|
from .resources.migration import Migration
|
|
11
13
|
from .resources.opt_in_forms import OptInForms
|
|
12
14
|
from .resources.segments import Segments
|
|
13
15
|
from .resources.sequences import Sequences
|
|
14
16
|
from .resources.subscribers import Subscribers
|
|
17
|
+
from .resources.suppressions import Suppressions
|
|
15
18
|
from .resources.templates import Templates
|
|
16
19
|
from .resources.transactionals import Transactionals
|
|
20
|
+
from .resources.users import Users
|
|
17
21
|
from .resources.webhook_endpoints import WebhookEndpoints
|
|
18
22
|
|
|
19
23
|
Id = Union[str, int]
|
|
@@ -37,14 +41,25 @@ class Broadcast:
|
|
|
37
41
|
self.broadcasts = Broadcasts(self)
|
|
38
42
|
self.segments = Segments(self)
|
|
39
43
|
self.templates = Templates(self)
|
|
44
|
+
#: The channel's brand kit (Settings -> Design). Read-only.
|
|
45
|
+
self.channel_design = ChannelDesign(self)
|
|
40
46
|
self.webhook_endpoints = WebhookEndpoints(self)
|
|
41
47
|
self.transactionals = Transactionals(self)
|
|
42
48
|
self.opt_in_forms = OptInForms(self)
|
|
43
49
|
self.email_servers = EmailServers(self)
|
|
44
50
|
self.autopilots = Autopilots(self)
|
|
45
51
|
self.discovery = Discovery(self)
|
|
52
|
+
#: The current channel's suppression list (plus ``check``, which
|
|
53
|
+
#: reads the global list too).
|
|
54
|
+
self.suppressions = Suppressions(self)
|
|
55
|
+
#: The installation-wide suppression list. Requires an admin (system)
|
|
56
|
+
#: API token.
|
|
57
|
+
self.global_suppressions = GlobalSuppressions(self)
|
|
46
58
|
#: Read-only export endpoints. Requires an admin (system) API token.
|
|
47
59
|
self.migration = Migration(self)
|
|
60
|
+
#: Installation users and their permissions. Requires an admin
|
|
61
|
+
#: (system) API token; sudo users are read-only through this API.
|
|
62
|
+
self.users = Users(self)
|
|
48
63
|
|
|
49
64
|
# --- Channel scoping (admin/system tokens) ---
|
|
50
65
|
|
|
@@ -97,6 +112,9 @@ class Broadcast:
|
|
|
97
112
|
def skill(self) -> str:
|
|
98
113
|
return self.discovery.skill()
|
|
99
114
|
|
|
115
|
+
def openapi(self) -> str:
|
|
116
|
+
return self.discovery.openapi()
|
|
117
|
+
|
|
100
118
|
# --- Internal ---
|
|
101
119
|
|
|
102
120
|
def request(
|
|
@@ -25,6 +25,9 @@ class BaseResource:
|
|
|
25
25
|
def _patch(self, path: str, body: Optional[Dict[str, Any]] = None) -> Any:
|
|
26
26
|
return self._client.request("PATCH", path, body or {})
|
|
27
27
|
|
|
28
|
+
def _put(self, path: str, body: Optional[Dict[str, Any]] = None) -> Any:
|
|
29
|
+
return self._client.request("PUT", path, body or {})
|
|
30
|
+
|
|
28
31
|
def _delete(self, path: str, body: Optional[Dict[str, Any]] = None) -> Any:
|
|
29
32
|
return self._client.request("DELETE", path, body)
|
|
30
33
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from .base import BaseResource
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ChannelDesign(BaseResource):
|
|
7
|
+
"""The brand kit of the token's channel (Settings -> Design). Read-only.
|
|
8
|
+
|
|
9
|
+
Requires the ``templates_read`` permission: the kit is design metadata for
|
|
10
|
+
templates. There is no channel argument; the server reads the channel the
|
|
11
|
+
token resolves to (for an admin token, the one set with ``with_channel`` or
|
|
12
|
+
``broadcast_channel_id``).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def get(self) -> Any:
|
|
16
|
+
"""The channel's brand kit, fully resolved (defaults filled in).
|
|
17
|
+
|
|
18
|
+
Returns ``colors``, ``typography`` (``font`` key and the email-safe
|
|
19
|
+
``font_stack``), ``layout`` (``width``, ``radius``), and ``brand``
|
|
20
|
+
(``logo_url`` as a public URL or None, ``logo_width``, ``website_url``,
|
|
21
|
+
``social_links``, ``social_icon_style``). Block emails built with the
|
|
22
|
+
drag-and-drop editor inherit these values.
|
|
23
|
+
"""
|
|
24
|
+
return self._get("/api/v1/channel/design")
|
|
@@ -33,3 +33,17 @@ class Discovery(BaseResource):
|
|
|
33
33
|
Returns a ``str``, not a dict — this endpoint serves ``text/plain``.
|
|
34
34
|
"""
|
|
35
35
|
return self._get("/api/v1/skill", raw=True)
|
|
36
|
+
|
|
37
|
+
def openapi(self) -> str:
|
|
38
|
+
"""This installation's own OpenAPI document, as YAML.
|
|
39
|
+
|
|
40
|
+
Returns a ``str``, not a dict — this endpoint serves
|
|
41
|
+
``application/yaml``.
|
|
42
|
+
|
|
43
|
+
The server URL inside the document is rewritten by the installation to
|
|
44
|
+
the host that served it, so the result feeds a client generator or an
|
|
45
|
+
API explorer without hand-editing. Preferable to a spec copied from
|
|
46
|
+
elsewhere: a 2.28 install serves the 2.28 surface, so the document
|
|
47
|
+
cannot drift from the routes it describes.
|
|
48
|
+
"""
|
|
49
|
+
return self._get("/api/v1/openapi", raw=True)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""The installation-wide suppression list.
|
|
2
|
+
|
|
3
|
+
Addresses on it never receive mail from any channel. All operations require an
|
|
4
|
+
admin (system) API token — a channel token gets a 401.
|
|
5
|
+
|
|
6
|
+
There is deliberately no ``check`` here: checking is a per-channel question
|
|
7
|
+
(it reads the channel list too), so it lives on ``suppressions``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, List
|
|
11
|
+
|
|
12
|
+
from .base import BaseResource
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GlobalSuppressions(BaseResource):
|
|
16
|
+
def list(self, **params: Any) -> Any:
|
|
17
|
+
"""List global suppressions (250 per page, with ``pagination``
|
|
18
|
+
metadata; pass ``page``). Optional ``email`` filters by partial
|
|
19
|
+
match."""
|
|
20
|
+
return self._get("/api/v1/global_suppressions.json", params)
|
|
21
|
+
|
|
22
|
+
def add(self, email: str) -> Any:
|
|
23
|
+
"""Add an address to the global list. Already-suppressed is a success
|
|
24
|
+
(200 instead of 201)."""
|
|
25
|
+
return self._post("/api/v1/global_suppressions.json", {"email": email})
|
|
26
|
+
|
|
27
|
+
def remove(self, email: str) -> Any:
|
|
28
|
+
"""Remove an address from the global list only. Channels that
|
|
29
|
+
suppressed the same address on their own account keep their block."""
|
|
30
|
+
return self._delete("/api/v1/global_suppressions.json", {"email": email})
|
|
31
|
+
|
|
32
|
+
def bulk_add(self, emails: List[str]) -> Any:
|
|
33
|
+
"""Add up to 10,000 addresses at once. Idempotent. Returns ``added``,
|
|
34
|
+
``already_suppressed``, and ``invalid`` counts."""
|
|
35
|
+
return self._post("/api/v1/global_suppressions/bulk.json", {"emails": emails})
|
|
36
|
+
|
|
37
|
+
def bulk_remove(self, emails: List[str]) -> Any:
|
|
38
|
+
"""Remove up to 10,000 addresses at once. Returns ``removed`` and
|
|
39
|
+
``not_found`` counts."""
|
|
40
|
+
return self._delete("/api/v1/global_suppressions/bulk.json", {"emails": emails})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""The current channel's suppression list.
|
|
2
|
+
|
|
3
|
+
Addresses on it never receive broadcasts, sequences, or transactionals from
|
|
4
|
+
this channel. The installation-wide list is a separate resource — see
|
|
5
|
+
``global_suppressions`` — but ``check`` reads across both on purpose: it
|
|
6
|
+
answers the question an integration actually asks, "will this address receive
|
|
7
|
+
mail?".
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, List
|
|
11
|
+
|
|
12
|
+
from .base import BaseResource
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Suppressions(BaseResource):
|
|
16
|
+
def list(self, **params: Any) -> Any:
|
|
17
|
+
"""List the channel's suppressions (250 per page, with ``pagination``
|
|
18
|
+
metadata; pass ``page``). Optional ``email`` filters by partial,
|
|
19
|
+
case-insensitive match."""
|
|
20
|
+
return self._get("/api/v1/suppressions.json", params)
|
|
21
|
+
|
|
22
|
+
def add(self, email: str) -> Any:
|
|
23
|
+
"""Add an address to the channel's suppression list.
|
|
24
|
+
|
|
25
|
+
Adding an address that is already suppressed is a success (the server
|
|
26
|
+
answers 200 instead of 201), so callers do not have to check first.
|
|
27
|
+
"""
|
|
28
|
+
return self._post("/api/v1/suppressions.json", {"email": email})
|
|
29
|
+
|
|
30
|
+
def remove(self, email: str) -> Any:
|
|
31
|
+
"""Remove an address from the channel's suppression list. Returns
|
|
32
|
+
``removed: False`` (not an error) when the address was not on it.
|
|
33
|
+
Does not touch the global list."""
|
|
34
|
+
return self._delete("/api/v1/suppressions.json", {"email": email})
|
|
35
|
+
|
|
36
|
+
def bulk_add(self, emails: List[str]) -> Any:
|
|
37
|
+
"""Add up to 10,000 addresses at once. Idempotent: a retried batch
|
|
38
|
+
cannot duplicate. Returns ``added``, ``already_suppressed``, and
|
|
39
|
+
``invalid`` counts."""
|
|
40
|
+
return self._post("/api/v1/suppressions/bulk.json", {"emails": emails})
|
|
41
|
+
|
|
42
|
+
def bulk_remove(self, emails: List[str]) -> Any:
|
|
43
|
+
"""Remove up to 10,000 addresses at once. Returns ``removed`` and
|
|
44
|
+
``not_found`` counts."""
|
|
45
|
+
return self._delete("/api/v1/suppressions/bulk.json", {"emails": emails})
|
|
46
|
+
|
|
47
|
+
def check(self, email: str) -> Any:
|
|
48
|
+
"""Will this address receive mail? Reads across both the global and
|
|
49
|
+
the channel list — a globally blocked address reports
|
|
50
|
+
``suppressed: True`` here even though it is absent from the channel's
|
|
51
|
+
own list. The response's ``scope`` says which list matched."""
|
|
52
|
+
return self._get("/api/v1/suppressions/check.json", {"email": email})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union
|
|
2
|
+
|
|
3
|
+
from .base import BaseResource, compact
|
|
4
|
+
|
|
5
|
+
Id = Union[str, int]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Users(BaseResource):
|
|
9
|
+
"""Manage installation users and their permissions.
|
|
10
|
+
|
|
11
|
+
Every operation here requires an **admin API token** — a channel token
|
|
12
|
+
gets ``403 {"error": "Admin API token required for user management"}``.
|
|
13
|
+
Reads need the ``users_read`` scope, writes need ``users_write``.
|
|
14
|
+
|
|
15
|
+
Sudo users are **read-only** through this API: update, deactivate,
|
|
16
|
+
activate, delete, and permission writes on a sudo user all raise
|
|
17
|
+
``AuthorizationError`` (403). Sudo access itself can never be granted
|
|
18
|
+
through the API, whether creating or updating a user or writing system
|
|
19
|
+
permissions.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def list(
|
|
23
|
+
self,
|
|
24
|
+
limit: Optional[int] = None,
|
|
25
|
+
offset: Optional[int] = None,
|
|
26
|
+
q: Optional[str] = None,
|
|
27
|
+
status: Optional[str] = None,
|
|
28
|
+
) -> Any:
|
|
29
|
+
"""List users. ``q`` searches email/name, ``status`` is ``"active"``
|
|
30
|
+
or ``"inactive"``."""
|
|
31
|
+
params = compact({"limit": limit, "offset": offset, "q": q, "status": status})
|
|
32
|
+
return self._get("/api/v1/users", params)
|
|
33
|
+
|
|
34
|
+
def get(self, id: Id) -> Any: # noqa: A002
|
|
35
|
+
return self._get("/api/v1/users/{}".format(id))
|
|
36
|
+
|
|
37
|
+
def create(self, **attrs: Any) -> Any:
|
|
38
|
+
"""Create a user. Requires either ``password`` or
|
|
39
|
+
``send_password_reset=True``."""
|
|
40
|
+
return self._post("/api/v1/users", {"user": attrs})
|
|
41
|
+
|
|
42
|
+
def update(self, id: Id, **attrs: Any) -> Any: # noqa: A002
|
|
43
|
+
"""Update a user. Raises ``AuthorizationError`` if the user is sudo."""
|
|
44
|
+
return self._patch("/api/v1/users/{}".format(id), {"user": attrs})
|
|
45
|
+
|
|
46
|
+
def deactivate(self, id: Id) -> Any: # noqa: A002
|
|
47
|
+
return self._post("/api/v1/users/{}/deactivate".format(id))
|
|
48
|
+
|
|
49
|
+
def activate(self, id: Id) -> Any: # noqa: A002
|
|
50
|
+
"""Reactivate a user. Also clears any account lockout."""
|
|
51
|
+
return self._post("/api/v1/users/{}/activate".format(id))
|
|
52
|
+
|
|
53
|
+
def delete(self, id: Id) -> Any: # noqa: A002
|
|
54
|
+
return self._delete("/api/v1/users/{}".format(id))
|
|
55
|
+
|
|
56
|
+
def channel_permissions(self, id: Id) -> Any: # noqa: A002
|
|
57
|
+
return self._get("/api/v1/users/{}/channel_permissions".format(id))
|
|
58
|
+
|
|
59
|
+
def set_channel_permissions(
|
|
60
|
+
self,
|
|
61
|
+
id: Id, # noqa: A002
|
|
62
|
+
broadcast_channel_id: Id,
|
|
63
|
+
permissions: Optional[Dict[str, bool]] = None,
|
|
64
|
+
role: Optional[str] = None,
|
|
65
|
+
preset_id: Optional[Id] = None,
|
|
66
|
+
) -> Any:
|
|
67
|
+
"""Replace the user's whole permission record for one channel.
|
|
68
|
+
|
|
69
|
+
This is a PUT: it replaces the record rather than merging into it, so
|
|
70
|
+
passing ``permissions`` sets any flag not named to ``False``. Pass
|
|
71
|
+
exactly one of ``permissions``, ``role``, or ``preset_id``.
|
|
72
|
+
"""
|
|
73
|
+
body = _one_of(permissions=permissions, role=role, preset_id=preset_id)
|
|
74
|
+
return self._put("/api/v1/users/{}/channel_permissions/{}".format(id, broadcast_channel_id), body)
|
|
75
|
+
|
|
76
|
+
def remove_channel_permissions(self, id: Id, broadcast_channel_id: Id) -> Any: # noqa: A002
|
|
77
|
+
return self._delete("/api/v1/users/{}/channel_permissions/{}".format(id, broadcast_channel_id))
|
|
78
|
+
|
|
79
|
+
def bulk_channel_permissions(
|
|
80
|
+
self,
|
|
81
|
+
id: Id, # noqa: A002
|
|
82
|
+
broadcast_channel_ids: List[Id],
|
|
83
|
+
permissions: Optional[Dict[str, bool]] = None,
|
|
84
|
+
role: Optional[str] = None,
|
|
85
|
+
preset_id: Optional[Id] = None,
|
|
86
|
+
) -> Any:
|
|
87
|
+
"""Apply the same permission record to several channels at once.
|
|
88
|
+
|
|
89
|
+
Pass exactly one of ``permissions``, ``role``, or ``preset_id``.
|
|
90
|
+
Returns ``{"applied": [...], "failed": [...]}`` — a failure on one
|
|
91
|
+
channel does not roll back the others.
|
|
92
|
+
"""
|
|
93
|
+
body = _one_of(permissions=permissions, role=role, preset_id=preset_id)
|
|
94
|
+
body["broadcast_channel_ids"] = broadcast_channel_ids
|
|
95
|
+
return self._post("/api/v1/users/{}/channel_permissions/bulk".format(id), body)
|
|
96
|
+
|
|
97
|
+
def system_permissions(self, id: Id) -> Any: # noqa: A002
|
|
98
|
+
return self._get("/api/v1/users/{}/system_permissions".format(id))
|
|
99
|
+
|
|
100
|
+
def update_system_permissions(self, id: Id, permissions: Dict[str, bool]) -> Any: # noqa: A002
|
|
101
|
+
"""Update only the named system permission flags.
|
|
102
|
+
|
|
103
|
+
``sudo_access`` is never present and sending it raises a 422 —
|
|
104
|
+
sudo can never be granted through the API.
|
|
105
|
+
"""
|
|
106
|
+
return self._patch("/api/v1/users/{}/system_permissions".format(id), {"permissions": permissions})
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _one_of(**kwargs: Any) -> Dict[str, Any]:
|
|
110
|
+
given = {k: v for k, v in kwargs.items() if v is not None}
|
|
111
|
+
if len(given) != 1:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
"Pass exactly one of {} (got {})".format(", ".join(kwargs.keys()), len(given))
|
|
114
|
+
)
|
|
115
|
+
return given
|
broadcast_python/version.py
CHANGED
broadcast_python/webhook.py
CHANGED
|
@@ -30,6 +30,9 @@ SUBSCRIBER_EVENTS = (
|
|
|
30
30
|
"subscriber.unsubscribed",
|
|
31
31
|
"subscriber.bounced",
|
|
32
32
|
"subscriber.complained",
|
|
33
|
+
# One event for a whole-list purge, in place of a subscriber.deleted per row
|
|
34
|
+
"subscribers.purged",
|
|
35
|
+
"subscribers.purge_failed",
|
|
33
36
|
)
|
|
34
37
|
|
|
35
38
|
BROADCAST_EVENTS = (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: broadcast-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.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
|
|
@@ -26,20 +26,12 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
|
|
27
27
|
Official Python client for [Broadcast](https://sendbroadcast.net), the self-hosted email marketing platform.
|
|
28
28
|
|
|
29
|
-
Works with any Broadcast instance — self-hosted or SaaS. Covers **
|
|
29
|
+
Works with any Broadcast instance — self-hosted or SaaS. Covers **117/117 API operations**, verified against the API's generated OpenAPI document.
|
|
30
30
|
|
|
31
31
|
📖 **[Python SDK documentation](https://sendbroadcast.net/docs/python-sdk)** · [API reference](https://sendbroadcast.net/docs/api-authentication) · [All docs](https://sendbroadcast.net/docs)
|
|
32
32
|
|
|
33
33
|
Also available: [Ruby](https://github.com/send-broadcast/broadcast-ruby) · [PHP](https://github.com/send-broadcast/broadcast-php) · [Node/TypeScript](https://github.com/send-broadcast/broadcast-node)
|
|
34
34
|
|
|
35
|
-
> **Not yet on PyPI.** The package is complete and tested but unpublished, so
|
|
36
|
-
> `pip install broadcast-python` will not resolve. Install from the repository
|
|
37
|
-
> until it lands:
|
|
38
|
-
>
|
|
39
|
-
> ```bash
|
|
40
|
-
> pip install git+https://github.com/send-broadcast/broadcast-python
|
|
41
|
-
> ```
|
|
42
|
-
|
|
43
35
|
## Installation
|
|
44
36
|
|
|
45
37
|
```bash
|
|
@@ -254,6 +246,19 @@ client.opt_in_forms.duplicate(id, label="Copy")
|
|
|
254
246
|
|
|
255
247
|
Reading a segment recounts its members server-side, so `segments.get` is not free.
|
|
256
248
|
|
|
249
|
+
### Channel design (brand kit)
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
kit = client.channel_design.get()
|
|
253
|
+
kit["colors"]["accent"] # "#2563eb"
|
|
254
|
+
kit["typography"]["font_stack"] # email-safe CSS font stack
|
|
255
|
+
kit["brand"]["logo_url"] # public URL, or None
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Read-only, for the token's own channel, fully resolved (defaults filled in).
|
|
259
|
+
Needs `templates_read`. Block emails built with the drag-and-drop editor inherit
|
|
260
|
+
these values.
|
|
261
|
+
|
|
257
262
|
### Email servers
|
|
258
263
|
|
|
259
264
|
**Credential redaction guard.** The API returns credentials bullet-masked
|
|
@@ -268,6 +273,39 @@ client.email_servers.update(id, name="Renamed", smtp_password=server["smtp_passw
|
|
|
268
273
|
# -> sends only {"name": "Renamed"}, warns about the dropped field
|
|
269
274
|
```
|
|
270
275
|
|
|
276
|
+
### Users
|
|
277
|
+
|
|
278
|
+
Requires an **admin API token**. Sudo users are read-only through this API —
|
|
279
|
+
update, deactivate, activate, delete, and permission writes on one raise
|
|
280
|
+
`AuthorizationError`. Sudo access itself can never be granted through the API.
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
client.users.list(status="active", q="ada")
|
|
284
|
+
user = client.users.create(email="ada@example.com", first_name="Ada", last_name="L", password="x")
|
|
285
|
+
client.users.update(user["id"], first_name="Grace")
|
|
286
|
+
client.users.deactivate(user["id"])
|
|
287
|
+
client.users.activate(user["id"])
|
|
288
|
+
client.users.delete(user["id"])
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Channel and system permissions:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
client.users.channel_permissions(user["id"])
|
|
295
|
+
|
|
296
|
+
# PUT replaces the whole channel record -- pass exactly one of
|
|
297
|
+
# permissions, role, or preset_id
|
|
298
|
+
client.users.set_channel_permissions(user["id"], channel_id, role="Editor")
|
|
299
|
+
client.users.set_channel_permissions(user["id"], channel_id, permissions={"subscribers_read": True})
|
|
300
|
+
client.users.remove_channel_permissions(user["id"], channel_id)
|
|
301
|
+
|
|
302
|
+
# Same rule, applied to several channels at once
|
|
303
|
+
client.users.bulk_channel_permissions(user["id"], [channel_id, other_channel_id], role="Viewer")
|
|
304
|
+
|
|
305
|
+
client.users.system_permissions(user["id"])
|
|
306
|
+
client.users.update_system_permissions(user["id"], {"user_management": True})
|
|
307
|
+
```
|
|
308
|
+
|
|
271
309
|
### Autopilot
|
|
272
310
|
|
|
273
311
|
```python
|
|
@@ -370,7 +408,9 @@ every rejection rather than distinguishing them.
|
|
|
370
408
|
Pass the **raw** request body. Re-serialising a parsed dict changes the bytes
|
|
371
409
|
and verification will fail.
|
|
372
410
|
|
|
373
|
-
`broadcast_python.EVENT_TYPES` lists all
|
|
411
|
+
`broadcast_python.EVENT_TYPES` lists all 34 event names. `SUBSCRIBER_EVENTS`
|
|
412
|
+
includes `subscribers.purged` and `subscribers.purge_failed`: a purge of the
|
|
413
|
+
whole list sends one of these instead of a `subscriber.deleted` per subscriber.
|
|
374
414
|
|
|
375
415
|
---
|
|
376
416
|
|
|
@@ -386,11 +426,12 @@ integration requires.
|
|
|
386
426
|
| Sequences | `sequences_read` -- list, get, list steps | `sequences_write` -- create, update, delete, manage steps, enroll subscribers |
|
|
387
427
|
| Broadcasts | `broadcasts_read` -- list, get, statistics | `broadcasts_write` -- create, update, delete, send, schedule |
|
|
388
428
|
| Segments | `segments_read` -- list, get | `segments_write` -- create, update, delete |
|
|
389
|
-
| Templates | `templates_read` -- list, get | `templates_write` -- create, update, delete |
|
|
429
|
+
| Templates | `templates_read` -- list, get, channel_design.get | `templates_write` -- create, update, delete |
|
|
390
430
|
| Opt-In Forms | `opt_in_forms_read` -- list, get, analytics | `opt_in_forms_write` -- create, update, delete, create_variant, duplicate |
|
|
391
431
|
| Email Servers | `email_servers_read` -- list, get | `email_servers_write` -- create, update, delete, test_connection, copy_to_channel (admin) |
|
|
392
432
|
| Webhook Endpoints | `webhook_endpoints_read` -- list, get, deliveries | `webhook_endpoints_write` -- create, update, delete, test |
|
|
393
433
|
| Autopilot | `autopilot_read` -- list, get, runs | `autopilot_write` -- create, update, delete, activate, pause, deactivate, trigger_run |
|
|
434
|
+
| Users (admin token only) | `users_read` -- list, get, channel_permissions, system_permissions | `users_write` -- create, update, deactivate, activate, delete, permission writes |
|
|
394
435
|
|
|
395
436
|
---
|
|
396
437
|
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
broadcast_python/__init__.py,sha256=_mzCFewxAEutE4fJ7hRPpaHuQwXpliHt5BNRE-J80TY,1632
|
|
2
|
-
broadcast_python/client.py,sha256=
|
|
2
|
+
broadcast_python/client.py,sha256=9aS5Jhq06Um-nM0s9idUM2Cs_LkmJVG8THHqiHESbpA,5471
|
|
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=f52uUZ9_ePggmFw6jNA8hgza42_PJo1M9gSQXdKeymI,169
|
|
9
|
+
broadcast_python/webhook.py,sha256=eLSxUkZ75dqEMoKfjLiG6SAVSVmxa0TQlhPl9MwZKaM,3679
|
|
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
|
-
broadcast_python/resources/base.py,sha256
|
|
12
|
+
broadcast_python/resources/base.py,sha256=-_uG_bAy_sz2oGHe2GQ64zRcpef6WZdMe4oHGJYFqEw,1632
|
|
13
13
|
broadcast_python/resources/broadcasts.py,sha256=VuYkKJ3d3mcaShXciMqAPHH-OT8px559VeXNltqkx3A,1954
|
|
14
|
-
broadcast_python/resources/
|
|
14
|
+
broadcast_python/resources/channel_design.py,sha256=4xiJMGkiBjbCH35LQi_2DqrZV8n5mi6XXhzJf6sGpLM,970
|
|
15
|
+
broadcast_python/resources/discovery.py,sha256=B0BKq9hpRwd9gN4MWoPArz2dli4bQLZs9T6IYB8edC4,1960
|
|
15
16
|
broadcast_python/resources/email_servers.py,sha256=1_ZJEPwVjFi5rN6S_0n7WfFfmF5y3JDIcW0Ixak0Z24,2830
|
|
17
|
+
broadcast_python/resources/global_suppressions.py,sha256=5Nghq0AfQYLPEL_IAc_tmwGfovA5jpLmdw8yUxg6HpA,1730
|
|
16
18
|
broadcast_python/resources/migration.py,sha256=y-GqY4BWzdNnlc6TWu7wNmqcqzEWSroxva1KiMqAJQg,4099
|
|
17
19
|
broadcast_python/resources/opt_in_forms.py,sha256=ScoEKXejT27UfTL8iGxlsADO6g5Q-4xCpWOFtmM6q2E,2509
|
|
18
20
|
broadcast_python/resources/segments.py,sha256=ZfUJxiWPhERWzkzuRGVu09uWqbwyUbdr3NsKOEeuW8o,857
|
|
19
21
|
broadcast_python/resources/sequences.py,sha256=9IuKw13e_LYZMLZiBYrr0b-BPhAAAucdt8DUjQvbP80,2467
|
|
20
22
|
broadcast_python/resources/subscribers.py,sha256=DtUnHs1rj3LF7KACQRQFB-ofgAHl1WTiLluL4W7xbfw,3168
|
|
23
|
+
broadcast_python/resources/suppressions.py,sha256=3IHMkWHgU3ArxcDeMSzPDFWL1Ngag2-m04XNll31C8c,2354
|
|
21
24
|
broadcast_python/resources/templates.py,sha256=FP0KcP61KkfrRID1XeAQ8hRxxRyxBzt404nXmnBqXNY,1262
|
|
22
25
|
broadcast_python/resources/transactionals.py,sha256=iwe9aTHKpeEMtDxd_S2TVzdUVm5Phno-Vinrxu9bn6w,2940
|
|
26
|
+
broadcast_python/resources/users.py,sha256=25gPmWLI1rqQnS9gCOi_2esUjdz_e0Zfi7_7Y2x91mg,4813
|
|
23
27
|
broadcast_python/resources/webhook_endpoints.py,sha256=Llkry10QT3JavkmKok3IAayYKM1PD1-iI51YhW3RbpE,1180
|
|
24
|
-
broadcast_python-0.
|
|
25
|
-
broadcast_python-0.
|
|
26
|
-
broadcast_python-0.
|
|
27
|
-
broadcast_python-0.
|
|
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,,
|
|
File without changes
|