kstlib 0.0.1a0__py3-none-any.whl → 1.0.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.
- kstlib/__init__.py +266 -1
- kstlib/__main__.py +16 -0
- kstlib/alerts/__init__.py +110 -0
- kstlib/alerts/channels/__init__.py +36 -0
- kstlib/alerts/channels/base.py +197 -0
- kstlib/alerts/channels/email.py +227 -0
- kstlib/alerts/channels/slack.py +389 -0
- kstlib/alerts/exceptions.py +72 -0
- kstlib/alerts/manager.py +651 -0
- kstlib/alerts/models.py +142 -0
- kstlib/alerts/throttle.py +263 -0
- kstlib/auth/__init__.py +139 -0
- kstlib/auth/callback.py +399 -0
- kstlib/auth/config.py +502 -0
- kstlib/auth/errors.py +127 -0
- kstlib/auth/models.py +316 -0
- kstlib/auth/providers/__init__.py +14 -0
- kstlib/auth/providers/base.py +393 -0
- kstlib/auth/providers/oauth2.py +645 -0
- kstlib/auth/providers/oidc.py +821 -0
- kstlib/auth/session.py +338 -0
- kstlib/auth/token.py +482 -0
- kstlib/cache/__init__.py +50 -0
- kstlib/cache/decorator.py +261 -0
- kstlib/cache/strategies.py +516 -0
- kstlib/cli/__init__.py +8 -0
- kstlib/cli/app.py +195 -0
- kstlib/cli/commands/__init__.py +5 -0
- kstlib/cli/commands/auth/__init__.py +39 -0
- kstlib/cli/commands/auth/common.py +122 -0
- kstlib/cli/commands/auth/login.py +325 -0
- kstlib/cli/commands/auth/logout.py +74 -0
- kstlib/cli/commands/auth/providers.py +57 -0
- kstlib/cli/commands/auth/status.py +291 -0
- kstlib/cli/commands/auth/token.py +199 -0
- kstlib/cli/commands/auth/whoami.py +106 -0
- kstlib/cli/commands/config.py +89 -0
- kstlib/cli/commands/ops/__init__.py +39 -0
- kstlib/cli/commands/ops/attach.py +49 -0
- kstlib/cli/commands/ops/common.py +269 -0
- kstlib/cli/commands/ops/list_sessions.py +252 -0
- kstlib/cli/commands/ops/logs.py +49 -0
- kstlib/cli/commands/ops/start.py +98 -0
- kstlib/cli/commands/ops/status.py +138 -0
- kstlib/cli/commands/ops/stop.py +60 -0
- kstlib/cli/commands/rapi/__init__.py +60 -0
- kstlib/cli/commands/rapi/call.py +341 -0
- kstlib/cli/commands/rapi/list.py +99 -0
- kstlib/cli/commands/rapi/show.py +206 -0
- kstlib/cli/commands/secrets/__init__.py +35 -0
- kstlib/cli/commands/secrets/common.py +425 -0
- kstlib/cli/commands/secrets/decrypt.py +88 -0
- kstlib/cli/commands/secrets/doctor.py +743 -0
- kstlib/cli/commands/secrets/encrypt.py +242 -0
- kstlib/cli/commands/secrets/shred.py +96 -0
- kstlib/cli/common.py +86 -0
- kstlib/config/__init__.py +76 -0
- kstlib/config/exceptions.py +110 -0
- kstlib/config/export.py +225 -0
- kstlib/config/loader.py +963 -0
- kstlib/config/sops.py +287 -0
- kstlib/db/__init__.py +54 -0
- kstlib/db/aiosqlcipher.py +137 -0
- kstlib/db/cipher.py +112 -0
- kstlib/db/database.py +367 -0
- kstlib/db/exceptions.py +25 -0
- kstlib/db/pool.py +302 -0
- kstlib/helpers/__init__.py +35 -0
- kstlib/helpers/exceptions.py +11 -0
- kstlib/helpers/time_trigger.py +396 -0
- kstlib/kstlib.conf.yml +890 -0
- kstlib/limits.py +963 -0
- kstlib/logging/__init__.py +108 -0
- kstlib/logging/manager.py +633 -0
- kstlib/mail/__init__.py +42 -0
- kstlib/mail/builder.py +626 -0
- kstlib/mail/exceptions.py +27 -0
- kstlib/mail/filesystem.py +248 -0
- kstlib/mail/transport.py +224 -0
- kstlib/mail/transports/__init__.py +19 -0
- kstlib/mail/transports/gmail.py +268 -0
- kstlib/mail/transports/resend.py +324 -0
- kstlib/mail/transports/smtp.py +326 -0
- kstlib/meta.py +72 -0
- kstlib/metrics/__init__.py +88 -0
- kstlib/metrics/decorators.py +1090 -0
- kstlib/metrics/exceptions.py +14 -0
- kstlib/monitoring/__init__.py +116 -0
- kstlib/monitoring/_styles.py +163 -0
- kstlib/monitoring/cell.py +57 -0
- kstlib/monitoring/config.py +424 -0
- kstlib/monitoring/delivery.py +579 -0
- kstlib/monitoring/exceptions.py +63 -0
- kstlib/monitoring/image.py +220 -0
- kstlib/monitoring/kv.py +79 -0
- kstlib/monitoring/list.py +69 -0
- kstlib/monitoring/metric.py +88 -0
- kstlib/monitoring/monitoring.py +341 -0
- kstlib/monitoring/renderer.py +139 -0
- kstlib/monitoring/service.py +392 -0
- kstlib/monitoring/table.py +129 -0
- kstlib/monitoring/types.py +56 -0
- kstlib/ops/__init__.py +86 -0
- kstlib/ops/base.py +148 -0
- kstlib/ops/container.py +577 -0
- kstlib/ops/exceptions.py +209 -0
- kstlib/ops/manager.py +407 -0
- kstlib/ops/models.py +176 -0
- kstlib/ops/tmux.py +372 -0
- kstlib/ops/validators.py +287 -0
- kstlib/py.typed +0 -0
- kstlib/rapi/__init__.py +118 -0
- kstlib/rapi/client.py +875 -0
- kstlib/rapi/config.py +861 -0
- kstlib/rapi/credentials.py +887 -0
- kstlib/rapi/exceptions.py +213 -0
- kstlib/resilience/__init__.py +101 -0
- kstlib/resilience/circuit_breaker.py +440 -0
- kstlib/resilience/exceptions.py +95 -0
- kstlib/resilience/heartbeat.py +491 -0
- kstlib/resilience/rate_limiter.py +506 -0
- kstlib/resilience/shutdown.py +417 -0
- kstlib/resilience/watchdog.py +637 -0
- kstlib/secrets/__init__.py +29 -0
- kstlib/secrets/exceptions.py +19 -0
- kstlib/secrets/models.py +62 -0
- kstlib/secrets/providers/__init__.py +79 -0
- kstlib/secrets/providers/base.py +58 -0
- kstlib/secrets/providers/environment.py +66 -0
- kstlib/secrets/providers/keyring.py +107 -0
- kstlib/secrets/providers/kms.py +223 -0
- kstlib/secrets/providers/kwargs.py +101 -0
- kstlib/secrets/providers/sops.py +209 -0
- kstlib/secrets/resolver.py +221 -0
- kstlib/secrets/sensitive.py +130 -0
- kstlib/secure/__init__.py +23 -0
- kstlib/secure/fs.py +194 -0
- kstlib/secure/permissions.py +70 -0
- kstlib/ssl.py +347 -0
- kstlib/ui/__init__.py +23 -0
- kstlib/ui/exceptions.py +26 -0
- kstlib/ui/panels.py +484 -0
- kstlib/ui/spinner.py +864 -0
- kstlib/ui/tables.py +382 -0
- kstlib/utils/__init__.py +48 -0
- kstlib/utils/dict.py +36 -0
- kstlib/utils/formatting.py +338 -0
- kstlib/utils/http_trace.py +237 -0
- kstlib/utils/lazy.py +49 -0
- kstlib/utils/secure_delete.py +205 -0
- kstlib/utils/serialization.py +247 -0
- kstlib/utils/text.py +56 -0
- kstlib/utils/validators.py +124 -0
- kstlib/websocket/__init__.py +97 -0
- kstlib/websocket/exceptions.py +214 -0
- kstlib/websocket/manager.py +1102 -0
- kstlib/websocket/models.py +361 -0
- kstlib-1.0.0.dist-info/METADATA +201 -0
- kstlib-1.0.0.dist-info/RECORD +163 -0
- {kstlib-0.0.1a0.dist-info → kstlib-1.0.0.dist-info}/WHEEL +1 -1
- kstlib-1.0.0.dist-info/entry_points.txt +2 -0
- kstlib-1.0.0.dist-info/licenses/LICENSE.md +9 -0
- kstlib-0.0.1a0.dist-info/METADATA +0 -29
- kstlib-0.0.1a0.dist-info/RECORD +0 -6
- kstlib-0.0.1a0.dist-info/licenses/LICENSE.md +0 -5
- {kstlib-0.0.1a0.dist-info → kstlib-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""WebSocket client with proactive connection control.
|
|
2
|
+
|
|
3
|
+
This module provides WebSocketManager, an async WebSocket client that offers
|
|
4
|
+
proactive control over connections rather than just reactive reconnection.
|
|
5
|
+
|
|
6
|
+
The key differentiator is the ability to control WHEN to disconnect/reconnect
|
|
7
|
+
rather than just reacting to disconnections. This is essential for trading
|
|
8
|
+
applications where you want to avoid disconnections during critical operations.
|
|
9
|
+
|
|
10
|
+
Features:
|
|
11
|
+
- **Proactive Control**: User-controlled disconnect/reconnect timing
|
|
12
|
+
- **Auto-Reconnection**: Configurable reconnection strategies
|
|
13
|
+
- **Subscription Management**: Auto-resubscribe on reconnection
|
|
14
|
+
- **Statistics Tracking**: Proactive vs reactive disconnect metrics
|
|
15
|
+
- **Config-Driven**: Integrates with kstlib.conf.yml
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
Basic usage:
|
|
19
|
+
|
|
20
|
+
>>> from kstlib.websocket import WebSocketManager
|
|
21
|
+
>>> async def main(): # doctest: +SKIP
|
|
22
|
+
... async with WebSocketManager("wss://example.com/ws") as ws:
|
|
23
|
+
... async for message in ws.stream():
|
|
24
|
+
... print(message)
|
|
25
|
+
|
|
26
|
+
Proactive control for trading:
|
|
27
|
+
|
|
28
|
+
>>> def next_candle_in() -> float: # doctest: +SKIP
|
|
29
|
+
... '''Seconds until next 4H candle.'''
|
|
30
|
+
... ...
|
|
31
|
+
>>> async def trading(): # doctest: +SKIP
|
|
32
|
+
... ws = WebSocketManager(
|
|
33
|
+
... url="wss://stream.binance.com/ws/btcusdt@kline_4h",
|
|
34
|
+
... # Disconnect when > 30s until next candle
|
|
35
|
+
... should_disconnect=lambda: next_candle_in() > 30,
|
|
36
|
+
... # Reconnect when < 60s until next candle
|
|
37
|
+
... should_reconnect=lambda: next_candle_in() < 60,
|
|
38
|
+
... disconnect_check_interval=5.0,
|
|
39
|
+
... )
|
|
40
|
+
... async with ws:
|
|
41
|
+
... async for candle in ws.stream():
|
|
42
|
+
... if candle.get("k", {}).get("x"):
|
|
43
|
+
... process_candle(candle)
|
|
44
|
+
|
|
45
|
+
Note:
|
|
46
|
+
Requires the ``websockets`` package. Install with::
|
|
47
|
+
|
|
48
|
+
pip install kstlib[websocket]
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
from kstlib.websocket.exceptions import (
|
|
52
|
+
WebSocketClosedError,
|
|
53
|
+
WebSocketConnectionError,
|
|
54
|
+
WebSocketError,
|
|
55
|
+
WebSocketProtocolError,
|
|
56
|
+
WebSocketQueueFullError,
|
|
57
|
+
WebSocketReconnectError,
|
|
58
|
+
WebSocketTimeoutError,
|
|
59
|
+
)
|
|
60
|
+
from kstlib.websocket.models import (
|
|
61
|
+
ConnectionState,
|
|
62
|
+
DisconnectReason,
|
|
63
|
+
ReconnectStrategy,
|
|
64
|
+
WebSocketStats,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Lazy import for WebSocketManager to avoid ImportError if websockets not installed
|
|
68
|
+
_websocket_manager = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def __getattr__(name: str) -> type:
|
|
72
|
+
"""Lazy load WebSocketManager to avoid ImportError if websockets not installed."""
|
|
73
|
+
if name == "WebSocketManager":
|
|
74
|
+
global _websocket_manager
|
|
75
|
+
if _websocket_manager is None:
|
|
76
|
+
from kstlib.websocket.manager import WebSocketManager
|
|
77
|
+
|
|
78
|
+
_websocket_manager = WebSocketManager
|
|
79
|
+
return _websocket_manager
|
|
80
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
81
|
+
raise AttributeError(msg)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
__all__ = [
|
|
85
|
+
"ConnectionState",
|
|
86
|
+
"DisconnectReason",
|
|
87
|
+
"ReconnectStrategy",
|
|
88
|
+
"WebSocketClosedError",
|
|
89
|
+
"WebSocketConnectionError",
|
|
90
|
+
"WebSocketError",
|
|
91
|
+
"WebSocketManager",
|
|
92
|
+
"WebSocketProtocolError",
|
|
93
|
+
"WebSocketQueueFullError",
|
|
94
|
+
"WebSocketReconnectError",
|
|
95
|
+
"WebSocketStats",
|
|
96
|
+
"WebSocketTimeoutError",
|
|
97
|
+
]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""WebSocket exception hierarchy.
|
|
2
|
+
|
|
3
|
+
All WebSocket exceptions inherit from WebSocketError, allowing users to catch
|
|
4
|
+
all WebSocket-specific errors with a single except clause.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from kstlib.websocket import WebSocketError
|
|
8
|
+
>>> try:
|
|
9
|
+
... await manager.connect()
|
|
10
|
+
... except WebSocketError as e: # doctest: +SKIP
|
|
11
|
+
... print(f"WebSocket error: {e}")
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from kstlib.config.exceptions import KstlibError
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"WebSocketClosedError",
|
|
20
|
+
"WebSocketConnectionError",
|
|
21
|
+
"WebSocketError",
|
|
22
|
+
"WebSocketProtocolError",
|
|
23
|
+
"WebSocketQueueFullError",
|
|
24
|
+
"WebSocketReconnectError",
|
|
25
|
+
"WebSocketTimeoutError",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WebSocketError(KstlibError):
|
|
30
|
+
"""Base exception for all WebSocket errors.
|
|
31
|
+
|
|
32
|
+
All WebSocket-specific exceptions inherit from this class,
|
|
33
|
+
allowing for easy catching of any WebSocket error.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class WebSocketConnectionError(WebSocketError, ConnectionError):
|
|
38
|
+
"""Failed to establish WebSocket connection.
|
|
39
|
+
|
|
40
|
+
Raised when the initial connection or reconnection fails after
|
|
41
|
+
exhausting all retry attempts.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
url: The WebSocket URL that failed to connect.
|
|
45
|
+
attempts: Number of connection attempts made.
|
|
46
|
+
last_error: The underlying error from the last attempt.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
message: str,
|
|
52
|
+
*,
|
|
53
|
+
url: str = "",
|
|
54
|
+
attempts: int = 0,
|
|
55
|
+
last_error: BaseException | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Initialize connection error.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
message: Human-readable error description.
|
|
61
|
+
url: The WebSocket URL that failed to connect.
|
|
62
|
+
attempts: Number of connection attempts made.
|
|
63
|
+
last_error: The underlying error from the last attempt.
|
|
64
|
+
"""
|
|
65
|
+
super().__init__(message)
|
|
66
|
+
self.url = url
|
|
67
|
+
self.attempts = attempts
|
|
68
|
+
self.last_error = last_error
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class WebSocketClosedError(WebSocketError):
|
|
72
|
+
"""WebSocket connection was closed.
|
|
73
|
+
|
|
74
|
+
Raised when the connection is closed unexpectedly by the server
|
|
75
|
+
or due to a protocol error.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
code: WebSocket close code (1000-4999).
|
|
79
|
+
reason: Optional human-readable close reason.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
message: str,
|
|
85
|
+
*,
|
|
86
|
+
code: int = 1006,
|
|
87
|
+
reason: str = "",
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Initialize closed error.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
message: Human-readable error description.
|
|
93
|
+
code: WebSocket close code (1000-4999).
|
|
94
|
+
reason: Optional human-readable close reason.
|
|
95
|
+
"""
|
|
96
|
+
super().__init__(message)
|
|
97
|
+
self.code = code
|
|
98
|
+
self.reason = reason
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class WebSocketTimeoutError(WebSocketError, TimeoutError):
|
|
102
|
+
"""WebSocket operation timed out.
|
|
103
|
+
|
|
104
|
+
Raised when a WebSocket operation (connect, ping, receive) exceeds
|
|
105
|
+
its configured timeout.
|
|
106
|
+
|
|
107
|
+
Attributes:
|
|
108
|
+
operation: The operation that timed out (connect, ping, receive).
|
|
109
|
+
timeout: The timeout value in seconds.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
message: str,
|
|
115
|
+
*,
|
|
116
|
+
operation: str = "",
|
|
117
|
+
timeout: float = 0.0,
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Initialize timeout error.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
message: Human-readable error description.
|
|
123
|
+
operation: The operation that timed out.
|
|
124
|
+
timeout: The timeout value in seconds.
|
|
125
|
+
"""
|
|
126
|
+
super().__init__(message)
|
|
127
|
+
self.operation = operation
|
|
128
|
+
self.timeout = timeout
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class WebSocketReconnectError(WebSocketError):
|
|
132
|
+
"""Failed to reconnect after disconnection.
|
|
133
|
+
|
|
134
|
+
Raised when all reconnection attempts have been exhausted
|
|
135
|
+
without successfully re-establishing the connection.
|
|
136
|
+
|
|
137
|
+
Attributes:
|
|
138
|
+
attempts: Total number of reconnection attempts made.
|
|
139
|
+
last_error: The underlying error from the last attempt.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(
|
|
143
|
+
self,
|
|
144
|
+
message: str,
|
|
145
|
+
*,
|
|
146
|
+
attempts: int = 0,
|
|
147
|
+
last_error: BaseException | None = None,
|
|
148
|
+
) -> None:
|
|
149
|
+
"""Initialize reconnect error.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
message: Human-readable error description.
|
|
153
|
+
attempts: Total number of reconnection attempts made.
|
|
154
|
+
last_error: The underlying error from the last attempt.
|
|
155
|
+
"""
|
|
156
|
+
super().__init__(message)
|
|
157
|
+
self.attempts = attempts
|
|
158
|
+
self.last_error = last_error
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class WebSocketProtocolError(WebSocketError):
|
|
162
|
+
"""WebSocket protocol violation.
|
|
163
|
+
|
|
164
|
+
Raised when the server or client violates the WebSocket protocol,
|
|
165
|
+
such as sending malformed frames or invalid data.
|
|
166
|
+
|
|
167
|
+
Attributes:
|
|
168
|
+
protocol_error: Description of the protocol violation.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
message: str,
|
|
174
|
+
*,
|
|
175
|
+
protocol_error: str = "",
|
|
176
|
+
) -> None:
|
|
177
|
+
"""Initialize protocol error.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
message: Human-readable error description.
|
|
181
|
+
protocol_error: Description of the protocol violation.
|
|
182
|
+
"""
|
|
183
|
+
super().__init__(message)
|
|
184
|
+
self.protocol_error = protocol_error
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class WebSocketQueueFullError(WebSocketError):
|
|
188
|
+
"""Message queue is full.
|
|
189
|
+
|
|
190
|
+
Raised when the incoming message queue is full and cannot accept
|
|
191
|
+
more messages. This typically indicates the consumer is too slow.
|
|
192
|
+
|
|
193
|
+
Attributes:
|
|
194
|
+
queue_size: Maximum queue size that was exceeded.
|
|
195
|
+
dropped_count: Number of messages dropped due to queue overflow.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
message: str,
|
|
201
|
+
*,
|
|
202
|
+
queue_size: int = 0,
|
|
203
|
+
dropped_count: int = 0,
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Initialize queue full error.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
message: Human-readable error description.
|
|
209
|
+
queue_size: Maximum queue size that was exceeded.
|
|
210
|
+
dropped_count: Number of messages dropped due to queue overflow.
|
|
211
|
+
"""
|
|
212
|
+
super().__init__(message)
|
|
213
|
+
self.queue_size = queue_size
|
|
214
|
+
self.dropped_count = dropped_count
|