status302 1.5.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.
@@ -0,0 +1,5 @@
1
+ .venv/
2
+ .mypy_cache/
3
+ .pytest_cache/
4
+ dist/
5
+ __pycache__/
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.5
2
+ Name: status302
3
+ Version: 1.5.0
4
+ Summary: Official Python SDK for the Status302 API: typed REST client, realtime stream client and webhook verification.
5
+ Project-URL: Homepage, https://status302.com
6
+ Project-URL: Documentation, https://status302.com/docs/sdks/python/
7
+ Author: Status302
8
+ License-Expression: MIT
9
+ Keywords: api,market data,status302,tickets,websocket
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: httpx>=0.27
17
+ Requires-Dist: msgpack>=1.0
18
+ Requires-Dist: websockets>=13
19
+ Description-Content-Type: text/markdown
20
+
21
+ # status302 — Python SDK
22
+
23
+ Official Python client for the [Status302 API](https://status302.com/docs/): real-time ticket market data (catalog, metrics, sales, seat maps) over REST, a realtime WebSocket stream and signed webhooks.
24
+
25
+ - Typed methods for every public operation (`TypedDict` models generated from the public OpenAPI spec)
26
+ - Blocking `Status302` and asyncio `AsyncStatus302` clients on [httpx](https://www.python-httpx.org/)
27
+ - Automatic retries on `429` and `5xx` (idempotent requests) honouring `Retry-After`
28
+ - Pagination helpers (cursors and alert `nextBefore` ids) and typed errors (`PlanRequiredError`, `RateLimitError`, …) carrying the response's `request_id` (also appended to the message as `(request <id>)`; quote it to support)
29
+ - Conditional requests: `if_none_match` with a typed `NotModified` result, response ETags, and an opt-in ETag cache
30
+ - Stream client: bearer or ticket auth, reconnects with backoff, `since` resume per topic, resync notices, seq deduplication
31
+ - Webhook signature verification
32
+
33
+ The package version equals the version of the public API spec it was generated from.
34
+
35
+ ## Install
36
+
37
+ ```sh
38
+ pip install status302 # or: uv add status302
39
+ ```
40
+
41
+ Python ≥ 3.11.
42
+
43
+ ## Quickstart
44
+
45
+ ```python
46
+ import os
47
+ from status302 import Status302
48
+
49
+ with Status302(api_key=os.environ["STATUS302_API_KEY"]) as status302:
50
+ page = status302.list_events(country="US", limit=5)
51
+ for event in page["items"]:
52
+ print(event["id"], event["name"], event["startsAt"])
53
+
54
+ metrics = status302.get_event_metrics(page["items"][0]["id"])
55
+ for row in metrics["items"]:
56
+ print(row["scope"], row["floor"], row["currency"]) # money is integer minor units
57
+ ```
58
+
59
+ Create an API key in the app under **Settings → Developers**. Keys look like `tx_live_<keyId>_<secret>` and are sent as `Authorization: Bearer …`.
60
+
61
+ ### Pagination
62
+
63
+ ```python
64
+ for event in status302.paginate(status302.list_events, country="US", limit=200):
65
+ ...
66
+ ```
67
+
68
+ `pages(...)` yields whole pages instead. Both follow `nextCursor` until it is null, or `nextBefore` (as `before`) for `list_alert_rules` and `list_alert_events`.
69
+
70
+ ### Conditional requests
71
+
72
+ ```python
73
+ from status302 import MemoryETagCache, NotModified, Status302
74
+
75
+ first = status302.with_response.get_event_metrics(4102) # APIResponse with .data, .etag, .status, .rate_limit
76
+ nxt = status302.get_event_metrics(4102, if_none_match=first.etag) # types.EventMetricsList | NotModified
77
+ if isinstance(nxt, NotModified):
78
+ print("unchanged since", nxt.etag)
79
+
80
+ cached = Status302(api_key=os.environ["STATUS302_API_KEY"], etag_cache=MemoryETagCache())
81
+ ```
82
+
83
+ Operations that accept `If-None-Match` take `if_none_match`; a `304` returns `NotModified` instead of raising, and the return type includes it only when the argument is passed. `with_response` has every operation returning the whole `APIResponse`. With `etag_cache` the client revalidates every `GET` by itself and returns the stored body on `304`; `MemoryETagCache(max_entries=1000)` is thread-safe and LRU, and any object with `get`/`set` (the `ETagCache` protocol) works.
84
+
85
+ ### Errors and rate limits
86
+
87
+ ```python
88
+ from status302 import PlanRequiredError, RateLimitError
89
+
90
+ try:
91
+ status302.list_movers(window="24h")
92
+ except PlanRequiredError as e:
93
+ print("needs plan", e.required_plan)
94
+ except RateLimitError as e:
95
+ print("retry in", e.retry_after, "s; quota:", e.rate_limit)
96
+ ```
97
+
98
+ `429` responses are retried after `Retry-After` (up to `max_retry_after`, default 60 s); `5xx` and connection errors are retried for idempotent methods with exponential backoff (`max_retries`, default 3). `status302.request(...)` returns the body together with status, headers, ETag and the parsed `RateLimit` state.
99
+
100
+ ### Realtime stream
101
+
102
+ ```python
103
+ import asyncio, os
104
+ from status302 import AsyncStatus302, DataMessage, ResyncMessage
105
+
106
+ async def main() -> None:
107
+ async with AsyncStatus302(api_key=os.environ["STATUS302_API_KEY"]) as status302:
108
+ async with status302.stream() as stream:
109
+ stream.subscribe("sales", filter={"country": ["US"]})
110
+ async for message in stream:
111
+ if isinstance(message, DataMessage):
112
+ print(message.seq, message.data)
113
+ elif isinstance(message, ResyncMessage):
114
+ snapshot = await stream.fetch_snapshot(message)
115
+
116
+ asyncio.run(main())
117
+ ```
118
+
119
+ The key needs the `stream` scope to connect, and each topic the scope of its REST equivalent (`event.{id}.kpis` → `market:read`, `sales`/`event.{id}.sales` → `sales:read`, `event.{id}.primary` → `maps:read` or `market:read`, `onsales` → `catalog:read`, `alerts` → `alerts:write`); without it the subscription fails with a `forbidden_topic` `SubscriptionError`. Server-side code authenticates with the key on the upgrade request (`auth="bearer"`, default with a key); pass `auth="ticket"` and `get_ticket=` to connect with single-use tickets instead (same scopes as the key).
120
+
121
+ #### Binary encoding (MessagePack)
122
+
123
+ `stream(encoding="msgpack")` asks the server for binary MessagePack frames (`encoding=msgpack` on the URL) instead of JSON text. Messages and `data` payloads decode to the same values as with JSON, so iteration doesn't change; the client's own messages stay JSON. Default: `"json"`.
124
+
125
+ ### Webhooks
126
+
127
+ ```python
128
+ from status302 import verify_webhook, WebhookVerificationError
129
+
130
+ event = verify_webhook(raw_body, headers["Status302-Signature"], os.environ["STATUS302_WEBHOOK_SECRET"])
131
+ ```
132
+
133
+ ## Versioning
134
+
135
+ Releases share the version of the API spec they were generated from (`status302.__version__`); `/v1` changes are additive only, so new fields, operations and enum values arrive in minor releases. See the [changelog](https://status302.com/docs/changelog/).
136
+
137
+ ## License
138
+
139
+ MIT
@@ -0,0 +1,119 @@
1
+ # status302 — Python SDK
2
+
3
+ Official Python client for the [Status302 API](https://status302.com/docs/): real-time ticket market data (catalog, metrics, sales, seat maps) over REST, a realtime WebSocket stream and signed webhooks.
4
+
5
+ - Typed methods for every public operation (`TypedDict` models generated from the public OpenAPI spec)
6
+ - Blocking `Status302` and asyncio `AsyncStatus302` clients on [httpx](https://www.python-httpx.org/)
7
+ - Automatic retries on `429` and `5xx` (idempotent requests) honouring `Retry-After`
8
+ - Pagination helpers (cursors and alert `nextBefore` ids) and typed errors (`PlanRequiredError`, `RateLimitError`, …) carrying the response's `request_id` (also appended to the message as `(request <id>)`; quote it to support)
9
+ - Conditional requests: `if_none_match` with a typed `NotModified` result, response ETags, and an opt-in ETag cache
10
+ - Stream client: bearer or ticket auth, reconnects with backoff, `since` resume per topic, resync notices, seq deduplication
11
+ - Webhook signature verification
12
+
13
+ The package version equals the version of the public API spec it was generated from.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ pip install status302 # or: uv add status302
19
+ ```
20
+
21
+ Python ≥ 3.11.
22
+
23
+ ## Quickstart
24
+
25
+ ```python
26
+ import os
27
+ from status302 import Status302
28
+
29
+ with Status302(api_key=os.environ["STATUS302_API_KEY"]) as status302:
30
+ page = status302.list_events(country="US", limit=5)
31
+ for event in page["items"]:
32
+ print(event["id"], event["name"], event["startsAt"])
33
+
34
+ metrics = status302.get_event_metrics(page["items"][0]["id"])
35
+ for row in metrics["items"]:
36
+ print(row["scope"], row["floor"], row["currency"]) # money is integer minor units
37
+ ```
38
+
39
+ Create an API key in the app under **Settings → Developers**. Keys look like `tx_live_<keyId>_<secret>` and are sent as `Authorization: Bearer …`.
40
+
41
+ ### Pagination
42
+
43
+ ```python
44
+ for event in status302.paginate(status302.list_events, country="US", limit=200):
45
+ ...
46
+ ```
47
+
48
+ `pages(...)` yields whole pages instead. Both follow `nextCursor` until it is null, or `nextBefore` (as `before`) for `list_alert_rules` and `list_alert_events`.
49
+
50
+ ### Conditional requests
51
+
52
+ ```python
53
+ from status302 import MemoryETagCache, NotModified, Status302
54
+
55
+ first = status302.with_response.get_event_metrics(4102) # APIResponse with .data, .etag, .status, .rate_limit
56
+ nxt = status302.get_event_metrics(4102, if_none_match=first.etag) # types.EventMetricsList | NotModified
57
+ if isinstance(nxt, NotModified):
58
+ print("unchanged since", nxt.etag)
59
+
60
+ cached = Status302(api_key=os.environ["STATUS302_API_KEY"], etag_cache=MemoryETagCache())
61
+ ```
62
+
63
+ Operations that accept `If-None-Match` take `if_none_match`; a `304` returns `NotModified` instead of raising, and the return type includes it only when the argument is passed. `with_response` has every operation returning the whole `APIResponse`. With `etag_cache` the client revalidates every `GET` by itself and returns the stored body on `304`; `MemoryETagCache(max_entries=1000)` is thread-safe and LRU, and any object with `get`/`set` (the `ETagCache` protocol) works.
64
+
65
+ ### Errors and rate limits
66
+
67
+ ```python
68
+ from status302 import PlanRequiredError, RateLimitError
69
+
70
+ try:
71
+ status302.list_movers(window="24h")
72
+ except PlanRequiredError as e:
73
+ print("needs plan", e.required_plan)
74
+ except RateLimitError as e:
75
+ print("retry in", e.retry_after, "s; quota:", e.rate_limit)
76
+ ```
77
+
78
+ `429` responses are retried after `Retry-After` (up to `max_retry_after`, default 60 s); `5xx` and connection errors are retried for idempotent methods with exponential backoff (`max_retries`, default 3). `status302.request(...)` returns the body together with status, headers, ETag and the parsed `RateLimit` state.
79
+
80
+ ### Realtime stream
81
+
82
+ ```python
83
+ import asyncio, os
84
+ from status302 import AsyncStatus302, DataMessage, ResyncMessage
85
+
86
+ async def main() -> None:
87
+ async with AsyncStatus302(api_key=os.environ["STATUS302_API_KEY"]) as status302:
88
+ async with status302.stream() as stream:
89
+ stream.subscribe("sales", filter={"country": ["US"]})
90
+ async for message in stream:
91
+ if isinstance(message, DataMessage):
92
+ print(message.seq, message.data)
93
+ elif isinstance(message, ResyncMessage):
94
+ snapshot = await stream.fetch_snapshot(message)
95
+
96
+ asyncio.run(main())
97
+ ```
98
+
99
+ The key needs the `stream` scope to connect, and each topic the scope of its REST equivalent (`event.{id}.kpis` → `market:read`, `sales`/`event.{id}.sales` → `sales:read`, `event.{id}.primary` → `maps:read` or `market:read`, `onsales` → `catalog:read`, `alerts` → `alerts:write`); without it the subscription fails with a `forbidden_topic` `SubscriptionError`. Server-side code authenticates with the key on the upgrade request (`auth="bearer"`, default with a key); pass `auth="ticket"` and `get_ticket=` to connect with single-use tickets instead (same scopes as the key).
100
+
101
+ #### Binary encoding (MessagePack)
102
+
103
+ `stream(encoding="msgpack")` asks the server for binary MessagePack frames (`encoding=msgpack` on the URL) instead of JSON text. Messages and `data` payloads decode to the same values as with JSON, so iteration doesn't change; the client's own messages stay JSON. Default: `"json"`.
104
+
105
+ ### Webhooks
106
+
107
+ ```python
108
+ from status302 import verify_webhook, WebhookVerificationError
109
+
110
+ event = verify_webhook(raw_body, headers["Status302-Signature"], os.environ["STATUS302_WEBHOOK_SECRET"])
111
+ ```
112
+
113
+ ## Versioning
114
+
115
+ Releases share the version of the API spec they were generated from (`status302.__version__`); `/v1` changes are additive only, so new fields, operations and enum values arrive in minor releases. See the [changelog](https://status302.com/docs/changelog/).
116
+
117
+ ## License
118
+
119
+ MIT
@@ -0,0 +1,42 @@
1
+ [project]
2
+ name = "status302"
3
+ dynamic = ["version"]
4
+ description = "Official Python SDK for the Status302 API: typed REST client, realtime stream client and webhook verification."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.11"
8
+ authors = [{ name = "Status302" }]
9
+ keywords = ["status302", "tickets", "market data", "api", "websocket"]
10
+ classifiers = [
11
+ "Development Status :: 5 - Production/Stable",
12
+ "Intended Audience :: Developers",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3 :: Only",
15
+ "Typing :: Typed",
16
+ ]
17
+ dependencies = ["httpx>=0.27", "msgpack>=1.0", "websockets>=13"]
18
+
19
+ [project.urls]
20
+ Homepage = "https://status302.com"
21
+ Documentation = "https://status302.com/docs/sdks/python/"
22
+
23
+ [dependency-groups]
24
+ dev = ["pytest>=8", "pyyaml>=6", "mypy>=1.13"]
25
+
26
+ [build-system]
27
+ requires = ["hatchling>=1.26"]
28
+ build-backend = "hatchling.build"
29
+
30
+ [tool.hatch.version]
31
+ path = "status302/_version.py"
32
+
33
+ [tool.hatch.build.targets.sdist]
34
+ include = ["status302", "README.md"]
35
+
36
+ [tool.pytest.ini_options]
37
+ testpaths = ["tests"]
38
+
39
+ [tool.mypy]
40
+ strict = true
41
+ # tests/test_client.py asserts the overloads of the generated methods (assert_type).
42
+ files = ["status302", "examples", "tests/test_client.py"]
@@ -0,0 +1,69 @@
1
+ """Official Python SDK for the Status302 API."""
2
+
3
+ from . import types
4
+ from ._base import APIResponse, CachedResponse, ETagCache, MemoryETagCache, NotModified
5
+ from ._client import AsyncStatus302, Status302
6
+ from ._errors import (
7
+ APIConnectionError,
8
+ APIError,
9
+ AuthenticationError,
10
+ BadRequestError,
11
+ ConflictError,
12
+ NotFoundError,
13
+ PermissionDeniedError,
14
+ PlanRequiredError,
15
+ RateLimitError,
16
+ ServerError,
17
+ Status302Error,
18
+ )
19
+ from ._ratelimit import RateLimitInfo, RateLimitPolicy, parse_rate_limit, parse_retry_after
20
+ from ._version import __version__
21
+ from .stream import (
22
+ DataMessage,
23
+ ErrorMessage,
24
+ ResyncMessage,
25
+ StreamClosedError,
26
+ Subscription,
27
+ SubscriptionError,
28
+ Status302Stream,
29
+ stream_url,
30
+ )
31
+ from .webhooks import WebhookVerificationError, sign_webhook, verify_webhook
32
+
33
+ __all__ = [
34
+ "APIConnectionError",
35
+ "APIError",
36
+ "APIResponse",
37
+ "AsyncStatus302",
38
+ "AuthenticationError",
39
+ "BadRequestError",
40
+ "CachedResponse",
41
+ "ConflictError",
42
+ "DataMessage",
43
+ "ETagCache",
44
+ "ErrorMessage",
45
+ "MemoryETagCache",
46
+ "NotFoundError",
47
+ "NotModified",
48
+ "PermissionDeniedError",
49
+ "PlanRequiredError",
50
+ "RateLimitError",
51
+ "RateLimitInfo",
52
+ "RateLimitPolicy",
53
+ "ResyncMessage",
54
+ "ServerError",
55
+ "StreamClosedError",
56
+ "Subscription",
57
+ "SubscriptionError",
58
+ "Status302",
59
+ "Status302Error",
60
+ "Status302Stream",
61
+ "WebhookVerificationError",
62
+ "__version__",
63
+ "parse_rate_limit",
64
+ "parse_retry_after",
65
+ "sign_webhook",
66
+ "stream_url",
67
+ "types",
68
+ "verify_webhook",
69
+ ]