broadcast-python 0.7.0__tar.gz → 0.8.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.
Files changed (37) hide show
  1. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/CHANGELOG.md +9 -0
  2. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/PKG-INFO +1 -28
  3. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/README.md +0 -27
  4. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/pyproject.toml +1 -1
  5. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/__init__.py +0 -2
  6. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/client.py +0 -2
  7. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/version.py +1 -1
  8. broadcast_python-0.7.0/src/broadcast_python/resources/migration.py +0 -114
  9. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/.gitignore +0 -0
  10. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/LICENSE +0 -0
  11. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/configuration.py +0 -0
  12. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/connection.py +0 -0
  13. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/errors.py +0 -0
  14. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/multipart.py +0 -0
  15. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/py.typed +0 -0
  16. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/__init__.py +0 -0
  17. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/autopilots.py +0 -0
  18. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/base.py +0 -0
  19. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/broadcasts.py +0 -0
  20. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/channel_design.py +0 -0
  21. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/channel_settings.py +0 -0
  22. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/discovery.py +0 -0
  23. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/email_servers.py +0 -0
  24. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/file_assets.py +0 -0
  25. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/global_suppressions.py +0 -0
  26. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/opt_in_forms.py +0 -0
  27. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/segments.py +0 -0
  28. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/sequences.py +0 -0
  29. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/subscribers.py +0 -0
  30. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/suppressions.py +0 -0
  31. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/templates.py +0 -0
  32. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/topics.py +0 -0
  33. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/transactionals.py +0 -0
  34. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/users.py +0 -0
  35. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/resources/webhook_endpoints.py +0 -0
  36. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/response.py +0 -0
  37. {broadcast_python-0.7.0 → broadcast_python-0.8.0}/src/broadcast_python/webhook.py +0 -0
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.8.0] - 2026-11-07
8
+
9
+ ### Removed
10
+
11
+ - **Breaking:** `client.migration` and the `COLLECTIONS` export: `manifest`,
12
+ the 19 export collections, `each_record` and `download_file_asset`.
13
+ Broadcast 3 removes the `/api/migration/v1` export API. A server before 3.0
14
+ still serves it; stay on 0.7.x to keep using it.
15
+
7
16
  ## [0.7.0] - 2026-10-17
8
17
 
9
18
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: broadcast-python
3
- Version: 0.7.0
3
+ Version: 0.8.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
@@ -465,30 +465,6 @@ client.prime() # full capability manifest
465
465
  client.skill() # plain-text agent skill manifest (a str)
466
466
  ```
467
467
 
468
- ### Migration / export
469
-
470
- Read-only. **Admin tokens only**, and every call needs a `broadcast_channel_id`.
471
-
472
- ```python
473
- client = Broadcast(..., broadcast_channel_id=42)
474
-
475
- client.migration.manifest()
476
-
477
- for sub in client.migration.each_record("subscribers"):
478
- ... # auto-pages; advances by the limit the server actually applied
479
-
480
- # Suppressed addresses live in two lists; export both:
481
- # suppressions = global suppression list, unsubscribed_emails = the channel's own
482
- for row in client.migration.each_record("unsubscribed_emails"):
483
- ... # {"id", "email", "broadcast_channel_id", "created_at", "updated_at"}
484
-
485
- data = client.migration.download_file_asset(id) # bytes
486
- ```
487
-
488
- On a demo instance this entire API returns **403 for every request**, valid
489
- token or not — deliberately, so a public demo cannot be used as a token oracle.
490
- It surfaces as `AuthorizationError`.
491
-
492
468
  ---
493
469
 
494
470
  ## Channel Scoping
@@ -571,9 +547,6 @@ The token is valid but lacks the permission for that call. Check the table
571
547
  above, then re-issue the key with the resource enabled — permissions are fixed
572
548
  at creation.
573
549
 
574
- On a demo instance, the entire migration API returns 403 for every request,
575
- valid token or not.
576
-
577
550
  ### `ValidationError` (422) on a repeated send
578
551
 
579
552
  If you reused an `idempotency_key` with a *different* payload, the API rejects
@@ -441,30 +441,6 @@ client.prime() # full capability manifest
441
441
  client.skill() # plain-text agent skill manifest (a str)
442
442
  ```
443
443
 
444
- ### Migration / export
445
-
446
- Read-only. **Admin tokens only**, and every call needs a `broadcast_channel_id`.
447
-
448
- ```python
449
- client = Broadcast(..., broadcast_channel_id=42)
450
-
451
- client.migration.manifest()
452
-
453
- for sub in client.migration.each_record("subscribers"):
454
- ... # auto-pages; advances by the limit the server actually applied
455
-
456
- # Suppressed addresses live in two lists; export both:
457
- # suppressions = global suppression list, unsubscribed_emails = the channel's own
458
- for row in client.migration.each_record("unsubscribed_emails"):
459
- ... # {"id", "email", "broadcast_channel_id", "created_at", "updated_at"}
460
-
461
- data = client.migration.download_file_asset(id) # bytes
462
- ```
463
-
464
- On a demo instance this entire API returns **403 for every request**, valid
465
- token or not — deliberately, so a public demo cannot be used as a token oracle.
466
- It surfaces as `AuthorizationError`.
467
-
468
444
  ---
469
445
 
470
446
  ## Channel Scoping
@@ -547,9 +523,6 @@ The token is valid but lacks the permission for that call. Check the table
547
523
  above, then re-issue the key with the resource enabled — permissions are fixed
548
524
  at creation.
549
525
 
550
- On a demo instance, the entire migration API returns 403 for every request,
551
- valid token or not.
552
-
553
526
  ### `ValidationError` (422) on a repeated send
554
527
 
555
528
  If you reused an `idempotency_key` with a *different* payload, the API rejects
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "broadcast-python"
7
- version = "0.7.0"
7
+ version = "0.8.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"
@@ -27,7 +27,6 @@ from .errors import (
27
27
  WarningError,
28
28
  )
29
29
  from .resources.email_servers import REDACTED_FIELDS
30
- from .resources.migration import COLLECTIONS
31
30
  from .resources.opt_in_forms import TRIGGER_FREQUENCIES
32
31
  from .resources.transactionals import MAX_IDEMPOTENCY_KEY_LENGTH
33
32
  from .response import RateLimit, Response, Warning_
@@ -37,7 +36,6 @@ from .webhook import EVENT_TYPES
37
36
  __version__ = VERSION
38
37
 
39
38
  __all__ = [
40
- "COLLECTIONS",
41
39
  "ENV_HOST",
42
40
  "ENV_TOKEN",
43
41
  "EVENT_TYPES",
@@ -11,7 +11,6 @@ from .resources.discovery import Discovery
11
11
  from .resources.email_servers import EmailServers
12
12
  from .resources.file_assets import FileAssets
13
13
  from .resources.global_suppressions import GlobalSuppressions
14
- from .resources.migration import Migration
15
14
  from .resources.opt_in_forms import OptInForms
16
15
  from .resources.segments import Segments
17
16
  from .resources.sequences import Sequences
@@ -65,7 +64,6 @@ class Broadcast:
65
64
  #: API token.
66
65
  self.global_suppressions = GlobalSuppressions(self)
67
66
  #: Read-only export endpoints. Requires an admin (system) API token.
68
- self.migration = Migration(self)
69
67
  #: Installation users and their permissions. Requires an admin
70
68
  #: (system) API token; sudo users are read-only through this API.
71
69
  self.users = Users(self)
@@ -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.7.0"
3
+ VERSION = "0.8.0"
@@ -1,114 +0,0 @@
1
- """Read-only export endpoints under ``/api/migration/v1``.
2
-
3
- Two things differ from the v1 API:
4
-
5
- 1. **Admin tokens only.** Channel-scoped tokens are rejected outright.
6
- 2. **broadcast_channel_id is required on every call.** Set it once via
7
- ``Broadcast(broadcast_channel_id=...)`` or ``client.with_channel(id)`` and it
8
- is attached automatically; otherwise pass it per call.
9
-
10
- On a demo instance (``DEMO_MODE``) this entire API returns 403 for every
11
- request, valid token or not, so a public demo cannot be used as a token oracle.
12
- That surfaces here as :class:`AuthorizationError`.
13
-
14
- Every list endpoint pages with ``limit`` (1..250, default 250) and ``offset``,
15
- returning ``{"data": [...], "pagination": {...}}``.
16
- """
17
-
18
- from typing import Any, Iterator, Union
19
-
20
- from .base import BaseResource
21
-
22
- Id = Union[str, int]
23
-
24
- #: Endpoints that are a plain paginated list of the channel's records.
25
- COLLECTIONS = (
26
- "channels",
27
- "subscribers",
28
- "templates",
29
- "segments",
30
- "sequences",
31
- "email_servers",
32
- "opt_in_forms",
33
- "broadcasts",
34
- "outbound_receipts",
35
- "webhook_endpoints",
36
- "tokens",
37
- "suppressions",
38
- "unsubscribed_emails",
39
- "tags",
40
- "users",
41
- "link_redirects",
42
- "link_clicks",
43
- "subscriber_histories",
44
- "file_assets",
45
- )
46
-
47
-
48
- class Migration(BaseResource):
49
- def manifest(self, **params: Any) -> Any:
50
- """Export summary: format version, channel identity, per-resource counts,
51
- and recent-history totals. Call this first to size an export.
52
-
53
- ``days_history`` windows the time-bounded counts; the server clamps to 1..365.
54
- """
55
- return self._get("/api/migration/v1/manifest", params)
56
-
57
- def download_file_asset(self, id: Id, **params: Any) -> bytes: # noqa: A002
58
- """Binary contents of a stored file asset — bytes, not JSON."""
59
- return self._get("/api/migration/v1/file_assets/{}/download".format(id), params, raw=True)
60
-
61
- def each_record(self, collection: str, limit: int = 250, **params: Any) -> Iterator[Any]:
62
- """Page through a collection, yielding each record.
63
-
64
- for sub in client.migration.each_record("subscribers"):
65
- ...
66
-
67
- Stops when the server reports ``has_more: False``, and advances by the
68
- limit the server actually applied rather than the one requested — the
69
- server clamps to 250, so trusting the request would skip records.
70
- """
71
- offset = 0
72
- while True:
73
- page = getattr(self, collection)(limit=limit, offset=offset, **params)
74
- records = page.get("data") or []
75
- yield from records
76
-
77
- pagination = page.get("pagination") or {}
78
- if not pagination.get("has_more"):
79
- return
80
-
81
- # `pagination.get("limit") or len(records)` would be wrong: Python's
82
- # `or` falls back on any falsy value, so a server-reported limit of
83
- # 0 would become len(records) and the loop would never terminate.
84
- # Ruby's `||` only falls back on nil, which is why the reference
85
- # implementation can spell it that way and this one cannot.
86
- advanced = pagination.get("limit")
87
- if advanced is None:
88
- advanced = len(records)
89
-
90
- try:
91
- advanced = int(advanced)
92
- except (TypeError, ValueError):
93
- return
94
- if advanced <= 0:
95
- return
96
-
97
- offset += advanced
98
-
99
-
100
- def _make_collection_method(name: str):
101
- def method(self, **params: Any) -> Any:
102
- return self._get("/api/migration/v1/{}".format(name), params)
103
-
104
- method.__name__ = name
105
- method.__qualname__ = "Migration.{}".format(name)
106
- method.__doc__ = "Paginated export of the channel's {}.".format(name.replace("_", " "))
107
- return method
108
-
109
-
110
- # Generated rather than hand-written: 19 near-identical methods invite the kind
111
- # of copy-paste drift this whole SDK family exists to prevent. Declared in
112
- # .api-coverage.yml so the coverage report still counts them.
113
- for _collection in COLLECTIONS:
114
- setattr(Migration, _collection, _make_collection_method(_collection))