radion-sdk 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.
radion/__init__.py ADDED
@@ -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"
radion/_config.py ADDED
@@ -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
radion/client.py ADDED
@@ -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
+ )
radion/errors.py ADDED
@@ -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
radion/py.typed ADDED
File without changes
@@ -0,0 +1,79 @@
1
+ """Radion realtime (WebSocket) product surface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .channels import (
6
+ CHANNELS,
7
+ FILTER_REQUIREMENTS,
8
+ Channel,
9
+ FilterKey,
10
+ MempoolChannel,
11
+ SubscribableChannel,
12
+ is_channel,
13
+ is_mempool_channel,
14
+ is_subscribable_channel,
15
+ )
16
+ from .client import RealtimeClient
17
+ from .dispatcher import EventDispatcher
18
+ from .payloads import (
19
+ ActivityPayload,
20
+ AnyChannelPayload,
21
+ AnyConfirmedPayload,
22
+ CollateralPayload,
23
+ CombosPayload,
24
+ LifecyclePayload,
25
+ OraclePayload,
26
+ PricesPayload,
27
+ TradesPayload,
28
+ )
29
+ from .protocol import (
30
+ ChannelEvent,
31
+ ChannelFilters,
32
+ ErrorFrame,
33
+ EventFrame,
34
+ InboundFrame,
35
+ PongFrame,
36
+ SubscribedFrame,
37
+ Subscription,
38
+ UnsubscribedFrame,
39
+ parse_inbound_frame,
40
+ validate_subscription_filters,
41
+ )
42
+ from .reconnect_manager import ReconnectManager
43
+ from .subscription_manager import SubscriptionManager
44
+
45
+ __all__ = [
46
+ "CHANNELS",
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
+ "RealtimeClient",
67
+ "ReconnectManager",
68
+ "SubscribableChannel",
69
+ "SubscribedFrame",
70
+ "Subscription",
71
+ "SubscriptionManager",
72
+ "TradesPayload",
73
+ "UnsubscribedFrame",
74
+ "is_channel",
75
+ "is_mempool_channel",
76
+ "is_subscribable_channel",
77
+ "parse_inbound_frame",
78
+ "validate_subscription_filters",
79
+ ]
@@ -0,0 +1,71 @@
1
+ """Typed channel names for the Radion realtime API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal, TypeGuard, get_args
6
+
7
+ Channel = Literal[
8
+ "global",
9
+ "trades",
10
+ "activity",
11
+ "lifecycle",
12
+ "oracle",
13
+ "collateral",
14
+ "combos",
15
+ "prices",
16
+ "wallets",
17
+ "markets",
18
+ "large_trades",
19
+ ]
20
+ """A confirmed channel name the SDK can subscribe to."""
21
+
22
+ CHANNELS: tuple[Channel, ...] = get_args(Channel)
23
+ """Every supported confirmed channel, as a tuple."""
24
+
25
+ MempoolChannel = Literal[
26
+ "mempool.global",
27
+ "mempool.trades",
28
+ "mempool.activity",
29
+ "mempool.lifecycle",
30
+ "mempool.oracle",
31
+ "mempool.collateral",
32
+ "mempool.combos",
33
+ "mempool.prices",
34
+ "mempool.wallets",
35
+ "mempool.markets",
36
+ "mempool.large_trades",
37
+ ]
38
+ """A ``mempool.``-prefixed companion channel."""
39
+
40
+ SubscribableChannel = Channel | MempoolChannel
41
+ """Any channel accepted by :meth:`RealtimeClient.subscribe`."""
42
+
43
+ FilterKey = Literal["wallets", "market_ids", "token_ids", "min_usd"]
44
+ """A server-side filter key."""
45
+
46
+ _MEMPOOL_PREFIX = "mempool."
47
+
48
+ #: Per-channel filter requirements. ``required_any_of`` means at least one of
49
+ #: the listed filters must be present. Channels absent from this map accept no
50
+ #: required filters. Mempool companions share their confirmed channel's rules.
51
+ FILTER_REQUIREMENTS: dict[Channel, tuple[FilterKey, ...]] = {
52
+ "wallets": ("wallets",),
53
+ "markets": ("market_ids", "token_ids"),
54
+ }
55
+
56
+
57
+ def is_channel(value: str) -> TypeGuard[Channel]:
58
+ """Return whether ``value`` is a known confirmed channel name."""
59
+ return value in CHANNELS
60
+
61
+
62
+ def is_mempool_channel(value: str) -> bool:
63
+ """Return whether ``value`` is a ``mempool.``-prefixed channel."""
64
+ return value.startswith(_MEMPOOL_PREFIX) and is_channel(
65
+ value[len(_MEMPOOL_PREFIX) :]
66
+ )
67
+
68
+
69
+ def is_subscribable_channel(value: str) -> bool:
70
+ """Return whether ``value`` is any subscribable channel."""
71
+ return is_channel(value) or is_mempool_channel(value)