radion-sdk 0.2.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.
- radion_sdk-0.2.0/.github/workflows/ci.yml +19 -0
- radion_sdk-0.2.0/.gitignore +9 -0
- radion_sdk-0.2.0/LICENSE +21 -0
- radion_sdk-0.2.0/PKG-INFO +210 -0
- radion_sdk-0.2.0/README.md +195 -0
- radion_sdk-0.2.0/pyproject.toml +30 -0
- radion_sdk-0.2.0/src/radion/__init__.py +85 -0
- radion_sdk-0.2.0/src/radion/_config.py +25 -0
- radion_sdk-0.2.0/src/radion/client.py +50 -0
- radion_sdk-0.2.0/src/radion/errors.py +28 -0
- radion_sdk-0.2.0/src/radion/py.typed +0 -0
- radion_sdk-0.2.0/src/radion/realtime/__init__.py +79 -0
- radion_sdk-0.2.0/src/radion/realtime/channels.py +71 -0
- radion_sdk-0.2.0/src/radion/realtime/client.py +370 -0
- radion_sdk-0.2.0/src/radion/realtime/dispatcher.py +93 -0
- radion_sdk-0.2.0/src/radion/realtime/heartbeat.py +59 -0
- radion_sdk-0.2.0/src/radion/realtime/payloads.py +223 -0
- radion_sdk-0.2.0/src/radion/realtime/protocol.py +207 -0
- radion_sdk-0.2.0/src/radion/realtime/reconnect_manager.py +42 -0
- radion_sdk-0.2.0/src/radion/realtime/subscription_manager.py +35 -0
- radion_sdk-0.2.0/uv.lock +201 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
check:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v5
|
|
15
|
+
with:
|
|
16
|
+
enable-cache: true
|
|
17
|
+
- run: uv sync --extra dev
|
|
18
|
+
- run: uv run ruff check
|
|
19
|
+
- run: uv run ty check
|
radion_sdk-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Radion
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: radion-sdk
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official async SDK for the Radion platform
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: async,polymarket,radion,realtime,sdk,websocket
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: msgspec>=0.18.0
|
|
10
|
+
Requires-Dist: websockets>=13.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: ty>=0.0.1a1; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# radion-sdk
|
|
17
|
+
|
|
18
|
+
[](https://pypi.org/project/radion-sdk/)
|
|
19
|
+
[](https://pypi.org/project/radion-sdk/)
|
|
20
|
+
[](./LICENSE)
|
|
21
|
+
|
|
22
|
+
Official, async-first, fully-typed SDK for the [Radion](https://radion.app) platform.
|
|
23
|
+
|
|
24
|
+
One client, one API key, every Radion product surface. Install `radion-sdk`; import it as `radion`.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
radion = Radion(api_key=os.getenv("RADION_API_KEY"))
|
|
28
|
+
await radion.realtime.connect()
|
|
29
|
+
await radion.realtime.subscribe(Subscription(id="trades", channel="trades"))
|
|
30
|
+
|
|
31
|
+
@radion.realtime.on("trades")
|
|
32
|
+
async def handle_trade(event):
|
|
33
|
+
print(event.id, event.data)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- **Unified client** — `Radion(api_key=...)` is the single entry point for every product surface
|
|
39
|
+
- **Auto-reconnect** — exponential backoff with jitter; stops on graceful shutdown
|
|
40
|
+
- **Subscription restore** — active channels are re-subscribed after every reconnect
|
|
41
|
+
- **Heartbeats** — ping/pong keep-alive that detects stale connections and reconnects
|
|
42
|
+
- **Typed end-to-end** — channel names, frame models, and errors
|
|
43
|
+
- **Async-first** — built on `asyncio`; handlers may be sync or async
|
|
44
|
+
|
|
45
|
+
## Requirements
|
|
46
|
+
|
|
47
|
+
- Python >= 3.10
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv add radion-sdk
|
|
53
|
+
# or: pip install radion-sdk
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import asyncio
|
|
60
|
+
import os
|
|
61
|
+
|
|
62
|
+
from radion import Radion, Subscription
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def main() -> None:
|
|
66
|
+
radion = Radion(api_key=os.getenv("RADION_API_KEY"))
|
|
67
|
+
|
|
68
|
+
await radion.realtime.connect()
|
|
69
|
+
await radion.realtime.subscribe(Subscription(id="trades", channel="trades"))
|
|
70
|
+
|
|
71
|
+
@radion.realtime.on("trades")
|
|
72
|
+
async def handle_trade(event):
|
|
73
|
+
print(event.id, event.channel, event.data)
|
|
74
|
+
|
|
75
|
+
await asyncio.sleep(60)
|
|
76
|
+
await radion.realtime.close()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
asyncio.run(main())
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
### Configuration
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
Radion(api_key, *, base_url=..., ws_url=..., reconnect=True, heartbeat=True,
|
|
88
|
+
heartbeat_interval=15.0, heartbeat_timeout=10.0)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| Argument | Type | Default | Description |
|
|
92
|
+
| -------------------- | ------- | ------------------------- | ------------------------------------------------- |
|
|
93
|
+
| `api_key` | `str` | — | **Required.** Sent as the `X-API-Key` header. |
|
|
94
|
+
| `base_url` | `str` | `https://api.radion.app` | Base URL for the Radion API. |
|
|
95
|
+
| `ws_url` | `str` | `wss://api.radion.app/ws` | Override the realtime endpoint. |
|
|
96
|
+
| `reconnect` | `bool` | `True` | Auto-reconnect on unexpected disconnect. |
|
|
97
|
+
| `heartbeat` | `bool` | `True` | Enable heartbeats / stale detection. |
|
|
98
|
+
| `heartbeat_interval` | `float` | `15.0` | Seconds between pings. |
|
|
99
|
+
| `heartbeat_timeout` | `float` | `10.0` | Extra grace before a quiet connection is stale. |
|
|
100
|
+
|
|
101
|
+
Extra keyword arguments are forwarded to the realtime client.
|
|
102
|
+
|
|
103
|
+
### Realtime client
|
|
104
|
+
|
|
105
|
+
`radion.realtime` is a `RealtimeClient`. It can also be imported and
|
|
106
|
+
constructed standalone:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from radion import RealtimeClient
|
|
110
|
+
|
|
111
|
+
client = RealtimeClient(api_key=os.getenv("RADION_API_KEY"))
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Method | Description |
|
|
115
|
+
| ------------------------------- | ------------------------------------------------------------ |
|
|
116
|
+
| `await connect()` | Open the connection. Resolves once established. |
|
|
117
|
+
| `await subscribe(subscription)` | Subscribe with `Subscription(id, channel, filters)`. Replayed on reconnect.|
|
|
118
|
+
| `await unsubscribe(id)` | Unsubscribe by subscription id. |
|
|
119
|
+
| `on(event)` | Decorator for a channel, `"event"` (all), or lifecycle handler. |
|
|
120
|
+
| `off(event, handler=None)` | Remove a handler (or all for that event). |
|
|
121
|
+
| `await close(code=1000, ...)` | Graceful shutdown. Stops reconnect attempts. |
|
|
122
|
+
| `connected` | Property — whether the socket is currently open. |
|
|
123
|
+
|
|
124
|
+
### Subscriptions & filters
|
|
125
|
+
|
|
126
|
+
A subscription is `Subscription(id, channel, filters=None)`. The `id` is your
|
|
127
|
+
own string, echoed back on every event so you can tell subscriptions apart;
|
|
128
|
+
`channel` may carry a `mempool.` prefix. Some channels require a filter:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from radion import ChannelFilters, Subscription
|
|
132
|
+
|
|
133
|
+
await radion.realtime.subscribe(
|
|
134
|
+
Subscription(id="whales", channel="large_trades", filters=ChannelFilters(min_usd=10_000))
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# "event" fires for every channel; the event carries id + channel + data.
|
|
138
|
+
@radion.realtime.on("event")
|
|
139
|
+
async def on_any(event):
|
|
140
|
+
print(event.id, event.channel, event.data)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`ChannelFilters`: `wallets`, `market_ids`, `token_ids`, `min_usd`.
|
|
144
|
+
|
|
145
|
+
### Channels
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
global · trades · activity · lifecycle · oracle · collateral
|
|
149
|
+
combos · prices · wallets · markets · large_trades
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Available as the `CHANNELS` tuple and the `Channel` type.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from radion import CHANNELS, Subscription
|
|
156
|
+
|
|
157
|
+
for channel in CHANNELS:
|
|
158
|
+
await radion.realtime.subscribe(Subscription(id=channel, channel=channel))
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Lifecycle events
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
@radion.realtime.on("open")
|
|
165
|
+
async def opened(_):
|
|
166
|
+
print("connected")
|
|
167
|
+
|
|
168
|
+
@radion.realtime.on("close")
|
|
169
|
+
async def closed(info):
|
|
170
|
+
print(info["code"], info["reason"])
|
|
171
|
+
|
|
172
|
+
@radion.realtime.on("reconnect")
|
|
173
|
+
async def reconnecting(info):
|
|
174
|
+
print(info["attempt"], info["delay"])
|
|
175
|
+
|
|
176
|
+
@radion.realtime.on("error")
|
|
177
|
+
async def errored(err):
|
|
178
|
+
print(err)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Reconnect & subscription restore
|
|
182
|
+
|
|
183
|
+
On an unexpected disconnect the client reconnects with exponential backoff and
|
|
184
|
+
re-sends every active subscription once the socket reopens. After `close()` no
|
|
185
|
+
further attempts run.
|
|
186
|
+
|
|
187
|
+
### Heartbeats
|
|
188
|
+
|
|
189
|
+
A ping is sent every `heartbeat_interval` seconds. Any inbound frame counts as
|
|
190
|
+
liveness; if the connection goes quiet past the timeout window it is terminated
|
|
191
|
+
and reconnected.
|
|
192
|
+
|
|
193
|
+
### Error handling
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from radion import RadionConnectionError, RadionServerError
|
|
197
|
+
|
|
198
|
+
@radion.realtime.on("error")
|
|
199
|
+
async def errored(err):
|
|
200
|
+
if isinstance(err, RadionServerError):
|
|
201
|
+
print("server error", err.code, err.channel)
|
|
202
|
+
elif isinstance(err, RadionConnectionError):
|
|
203
|
+
print("connection error", err)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
A throwing consumer handler is reported via the `error` event and never retried.
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
MIT
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# radion-sdk
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/radion-sdk/)
|
|
4
|
+
[](https://pypi.org/project/radion-sdk/)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
Official, async-first, fully-typed SDK for the [Radion](https://radion.app) platform.
|
|
8
|
+
|
|
9
|
+
One client, one API key, every Radion product surface. Install `radion-sdk`; import it as `radion`.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
radion = Radion(api_key=os.getenv("RADION_API_KEY"))
|
|
13
|
+
await radion.realtime.connect()
|
|
14
|
+
await radion.realtime.subscribe(Subscription(id="trades", channel="trades"))
|
|
15
|
+
|
|
16
|
+
@radion.realtime.on("trades")
|
|
17
|
+
async def handle_trade(event):
|
|
18
|
+
print(event.id, event.data)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Unified client** — `Radion(api_key=...)` is the single entry point for every product surface
|
|
24
|
+
- **Auto-reconnect** — exponential backoff with jitter; stops on graceful shutdown
|
|
25
|
+
- **Subscription restore** — active channels are re-subscribed after every reconnect
|
|
26
|
+
- **Heartbeats** — ping/pong keep-alive that detects stale connections and reconnects
|
|
27
|
+
- **Typed end-to-end** — channel names, frame models, and errors
|
|
28
|
+
- **Async-first** — built on `asyncio`; handlers may be sync or async
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Python >= 3.10
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv add radion-sdk
|
|
38
|
+
# or: pip install radion-sdk
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import asyncio
|
|
45
|
+
import os
|
|
46
|
+
|
|
47
|
+
from radion import Radion, Subscription
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def main() -> None:
|
|
51
|
+
radion = Radion(api_key=os.getenv("RADION_API_KEY"))
|
|
52
|
+
|
|
53
|
+
await radion.realtime.connect()
|
|
54
|
+
await radion.realtime.subscribe(Subscription(id="trades", channel="trades"))
|
|
55
|
+
|
|
56
|
+
@radion.realtime.on("trades")
|
|
57
|
+
async def handle_trade(event):
|
|
58
|
+
print(event.id, event.channel, event.data)
|
|
59
|
+
|
|
60
|
+
await asyncio.sleep(60)
|
|
61
|
+
await radion.realtime.close()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
asyncio.run(main())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
### Configuration
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
Radion(api_key, *, base_url=..., ws_url=..., reconnect=True, heartbeat=True,
|
|
73
|
+
heartbeat_interval=15.0, heartbeat_timeout=10.0)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Argument | Type | Default | Description |
|
|
77
|
+
| -------------------- | ------- | ------------------------- | ------------------------------------------------- |
|
|
78
|
+
| `api_key` | `str` | — | **Required.** Sent as the `X-API-Key` header. |
|
|
79
|
+
| `base_url` | `str` | `https://api.radion.app` | Base URL for the Radion API. |
|
|
80
|
+
| `ws_url` | `str` | `wss://api.radion.app/ws` | Override the realtime endpoint. |
|
|
81
|
+
| `reconnect` | `bool` | `True` | Auto-reconnect on unexpected disconnect. |
|
|
82
|
+
| `heartbeat` | `bool` | `True` | Enable heartbeats / stale detection. |
|
|
83
|
+
| `heartbeat_interval` | `float` | `15.0` | Seconds between pings. |
|
|
84
|
+
| `heartbeat_timeout` | `float` | `10.0` | Extra grace before a quiet connection is stale. |
|
|
85
|
+
|
|
86
|
+
Extra keyword arguments are forwarded to the realtime client.
|
|
87
|
+
|
|
88
|
+
### Realtime client
|
|
89
|
+
|
|
90
|
+
`radion.realtime` is a `RealtimeClient`. It can also be imported and
|
|
91
|
+
constructed standalone:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from radion import RealtimeClient
|
|
95
|
+
|
|
96
|
+
client = RealtimeClient(api_key=os.getenv("RADION_API_KEY"))
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Method | Description |
|
|
100
|
+
| ------------------------------- | ------------------------------------------------------------ |
|
|
101
|
+
| `await connect()` | Open the connection. Resolves once established. |
|
|
102
|
+
| `await subscribe(subscription)` | Subscribe with `Subscription(id, channel, filters)`. Replayed on reconnect.|
|
|
103
|
+
| `await unsubscribe(id)` | Unsubscribe by subscription id. |
|
|
104
|
+
| `on(event)` | Decorator for a channel, `"event"` (all), or lifecycle handler. |
|
|
105
|
+
| `off(event, handler=None)` | Remove a handler (or all for that event). |
|
|
106
|
+
| `await close(code=1000, ...)` | Graceful shutdown. Stops reconnect attempts. |
|
|
107
|
+
| `connected` | Property — whether the socket is currently open. |
|
|
108
|
+
|
|
109
|
+
### Subscriptions & filters
|
|
110
|
+
|
|
111
|
+
A subscription is `Subscription(id, channel, filters=None)`. The `id` is your
|
|
112
|
+
own string, echoed back on every event so you can tell subscriptions apart;
|
|
113
|
+
`channel` may carry a `mempool.` prefix. Some channels require a filter:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from radion import ChannelFilters, Subscription
|
|
117
|
+
|
|
118
|
+
await radion.realtime.subscribe(
|
|
119
|
+
Subscription(id="whales", channel="large_trades", filters=ChannelFilters(min_usd=10_000))
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# "event" fires for every channel; the event carries id + channel + data.
|
|
123
|
+
@radion.realtime.on("event")
|
|
124
|
+
async def on_any(event):
|
|
125
|
+
print(event.id, event.channel, event.data)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`ChannelFilters`: `wallets`, `market_ids`, `token_ids`, `min_usd`.
|
|
129
|
+
|
|
130
|
+
### Channels
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
global · trades · activity · lifecycle · oracle · collateral
|
|
134
|
+
combos · prices · wallets · markets · large_trades
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Available as the `CHANNELS` tuple and the `Channel` type.
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from radion import CHANNELS, Subscription
|
|
141
|
+
|
|
142
|
+
for channel in CHANNELS:
|
|
143
|
+
await radion.realtime.subscribe(Subscription(id=channel, channel=channel))
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Lifecycle events
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
@radion.realtime.on("open")
|
|
150
|
+
async def opened(_):
|
|
151
|
+
print("connected")
|
|
152
|
+
|
|
153
|
+
@radion.realtime.on("close")
|
|
154
|
+
async def closed(info):
|
|
155
|
+
print(info["code"], info["reason"])
|
|
156
|
+
|
|
157
|
+
@radion.realtime.on("reconnect")
|
|
158
|
+
async def reconnecting(info):
|
|
159
|
+
print(info["attempt"], info["delay"])
|
|
160
|
+
|
|
161
|
+
@radion.realtime.on("error")
|
|
162
|
+
async def errored(err):
|
|
163
|
+
print(err)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Reconnect & subscription restore
|
|
167
|
+
|
|
168
|
+
On an unexpected disconnect the client reconnects with exponential backoff and
|
|
169
|
+
re-sends every active subscription once the socket reopens. After `close()` no
|
|
170
|
+
further attempts run.
|
|
171
|
+
|
|
172
|
+
### Heartbeats
|
|
173
|
+
|
|
174
|
+
A ping is sent every `heartbeat_interval` seconds. Any inbound frame counts as
|
|
175
|
+
liveness; if the connection goes quiet past the timeout window it is terminated
|
|
176
|
+
and reconnected.
|
|
177
|
+
|
|
178
|
+
### Error handling
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from radion import RadionConnectionError, RadionServerError
|
|
182
|
+
|
|
183
|
+
@radion.realtime.on("error")
|
|
184
|
+
async def errored(err):
|
|
185
|
+
if isinstance(err, RadionServerError):
|
|
186
|
+
print("server error", err.code, err.channel)
|
|
187
|
+
elif isinstance(err, RadionConnectionError):
|
|
188
|
+
print("connection error", err)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
A throwing consumer handler is reported via the `error` event and never retried.
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "radion-sdk"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Official async SDK for the Radion platform"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
keywords = ["radion", "sdk", "realtime", "websocket", "async", "polymarket"]
|
|
9
|
+
dependencies = ["websockets>=13.0", "msgspec>=0.18.0"]
|
|
10
|
+
|
|
11
|
+
[project.optional-dependencies]
|
|
12
|
+
dev = ["ruff>=0.8.0", "ty>=0.0.1a1"]
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["hatchling"]
|
|
16
|
+
build-backend = "hatchling.build"
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.wheel]
|
|
19
|
+
packages = ["src/radion"]
|
|
20
|
+
|
|
21
|
+
[tool.ruff]
|
|
22
|
+
line-length = 88
|
|
23
|
+
target-version = "py310"
|
|
24
|
+
src = ["src"]
|
|
25
|
+
|
|
26
|
+
[tool.ruff.lint]
|
|
27
|
+
select = ["E", "F", "I", "UP", "B", "ASYNC", "SIM", "RUF"]
|
|
28
|
+
|
|
29
|
+
[tool.ty.environment]
|
|
30
|
+
root = ["./src"]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Official async SDK for the Radion platform."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ._config import DEFAULT_BASE_URL, DEFAULT_WS_URL, RadionConfig
|
|
6
|
+
from .client import Radion
|
|
7
|
+
from .errors import RadionConnectionError, RadionError, RadionServerError
|
|
8
|
+
from .realtime import (
|
|
9
|
+
CHANNELS,
|
|
10
|
+
FILTER_REQUIREMENTS,
|
|
11
|
+
ActivityPayload,
|
|
12
|
+
AnyChannelPayload,
|
|
13
|
+
AnyConfirmedPayload,
|
|
14
|
+
Channel,
|
|
15
|
+
ChannelEvent,
|
|
16
|
+
ChannelFilters,
|
|
17
|
+
CollateralPayload,
|
|
18
|
+
CombosPayload,
|
|
19
|
+
ErrorFrame,
|
|
20
|
+
EventDispatcher,
|
|
21
|
+
EventFrame,
|
|
22
|
+
FilterKey,
|
|
23
|
+
InboundFrame,
|
|
24
|
+
LifecyclePayload,
|
|
25
|
+
MempoolChannel,
|
|
26
|
+
OraclePayload,
|
|
27
|
+
PongFrame,
|
|
28
|
+
PricesPayload,
|
|
29
|
+
RealtimeClient,
|
|
30
|
+
ReconnectManager,
|
|
31
|
+
SubscribableChannel,
|
|
32
|
+
SubscribedFrame,
|
|
33
|
+
Subscription,
|
|
34
|
+
SubscriptionManager,
|
|
35
|
+
TradesPayload,
|
|
36
|
+
UnsubscribedFrame,
|
|
37
|
+
is_channel,
|
|
38
|
+
is_mempool_channel,
|
|
39
|
+
is_subscribable_channel,
|
|
40
|
+
validate_subscription_filters,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"CHANNELS",
|
|
45
|
+
"DEFAULT_BASE_URL",
|
|
46
|
+
"DEFAULT_WS_URL",
|
|
47
|
+
"FILTER_REQUIREMENTS",
|
|
48
|
+
"ActivityPayload",
|
|
49
|
+
"AnyChannelPayload",
|
|
50
|
+
"AnyConfirmedPayload",
|
|
51
|
+
"Channel",
|
|
52
|
+
"ChannelEvent",
|
|
53
|
+
"ChannelFilters",
|
|
54
|
+
"CollateralPayload",
|
|
55
|
+
"CombosPayload",
|
|
56
|
+
"ErrorFrame",
|
|
57
|
+
"EventDispatcher",
|
|
58
|
+
"EventFrame",
|
|
59
|
+
"FilterKey",
|
|
60
|
+
"InboundFrame",
|
|
61
|
+
"LifecyclePayload",
|
|
62
|
+
"MempoolChannel",
|
|
63
|
+
"OraclePayload",
|
|
64
|
+
"PongFrame",
|
|
65
|
+
"PricesPayload",
|
|
66
|
+
"Radion",
|
|
67
|
+
"RadionConfig",
|
|
68
|
+
"RadionConnectionError",
|
|
69
|
+
"RadionError",
|
|
70
|
+
"RadionServerError",
|
|
71
|
+
"RealtimeClient",
|
|
72
|
+
"ReconnectManager",
|
|
73
|
+
"SubscribableChannel",
|
|
74
|
+
"SubscribedFrame",
|
|
75
|
+
"Subscription",
|
|
76
|
+
"SubscriptionManager",
|
|
77
|
+
"TradesPayload",
|
|
78
|
+
"UnsubscribedFrame",
|
|
79
|
+
"is_channel",
|
|
80
|
+
"is_mempool_channel",
|
|
81
|
+
"is_subscribable_channel",
|
|
82
|
+
"validate_subscription_filters",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Shared configuration for the Radion SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
DEFAULT_BASE_URL = "https://api.radion.app"
|
|
8
|
+
"""Default base URL for the Radion REST API."""
|
|
9
|
+
|
|
10
|
+
DEFAULT_WS_URL = "wss://api.radion.app/ws"
|
|
11
|
+
"""Default endpoint for the Radion realtime (WebSocket) API."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class RadionConfig:
|
|
16
|
+
"""Shared configuration for every Radion product surface.
|
|
17
|
+
|
|
18
|
+
``base_url`` is reserved for the forthcoming REST resource namespaces
|
|
19
|
+
(``markets``, ``traders``, ``backtests``, …); the realtime client uses
|
|
20
|
+
``ws_url``.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
api_key: str
|
|
24
|
+
base_url: str = DEFAULT_BASE_URL
|
|
25
|
+
ws_url: str = DEFAULT_WS_URL
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""The unified :class:`Radion` platform client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ._config import DEFAULT_BASE_URL, DEFAULT_WS_URL
|
|
8
|
+
from .errors import RadionConnectionError
|
|
9
|
+
from .realtime import RealtimeClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Radion:
|
|
13
|
+
"""Unified async entry point for the Radion platform.
|
|
14
|
+
|
|
15
|
+
Holds shared configuration and exposes each product surface as an
|
|
16
|
+
attribute. Today that is :attr:`realtime`; REST resource namespaces
|
|
17
|
+
(``markets``, ``traders``, ``backtests``, ``auth``, ``health``) attach here
|
|
18
|
+
as they ship, built from ``base_url`` over a shared HTTP transport — the
|
|
19
|
+
constructor shape stays stable so adding them is purely additive.
|
|
20
|
+
|
|
21
|
+
Extra keyword arguments are forwarded to the realtime client (for example
|
|
22
|
+
``reconnect``, ``heartbeat``, ``heartbeat_interval``).
|
|
23
|
+
|
|
24
|
+
Example::
|
|
25
|
+
|
|
26
|
+
radion = Radion(api_key=os.getenv("RADION_API_KEY"))
|
|
27
|
+
await radion.realtime.connect()
|
|
28
|
+
await radion.realtime.subscribe("trades")
|
|
29
|
+
|
|
30
|
+
@radion.realtime.on("trades")
|
|
31
|
+
async def handle_trade(event):
|
|
32
|
+
print(event.data)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
*,
|
|
38
|
+
api_key: str,
|
|
39
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
40
|
+
ws_url: str = DEFAULT_WS_URL,
|
|
41
|
+
**realtime_options: Any,
|
|
42
|
+
) -> None:
|
|
43
|
+
if not api_key:
|
|
44
|
+
raise RadionConnectionError("api_key is required")
|
|
45
|
+
self._base_url = base_url
|
|
46
|
+
self.realtime = RealtimeClient(
|
|
47
|
+
api_key=api_key,
|
|
48
|
+
url=ws_url,
|
|
49
|
+
**realtime_options,
|
|
50
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Error types raised by the SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RadionError(Exception):
|
|
7
|
+
"""Base class for every error surfaced by the SDK."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RadionConnectionError(RadionError):
|
|
11
|
+
"""Raised when the SDK is used against an invalid connection state."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RadionServerError(RadionError):
|
|
15
|
+
"""Raised when the server reports an ``error`` frame."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
message: str,
|
|
20
|
+
*,
|
|
21
|
+
code: str | None = None,
|
|
22
|
+
channel: str | None = None,
|
|
23
|
+
id: str | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.code = code
|
|
27
|
+
self.channel = channel
|
|
28
|
+
self.id = id
|
|
File without changes
|