kstlib 0.0.1a0__py3-none-any.whl → 1.0.1__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.
Files changed (166) hide show
  1. kstlib/__init__.py +266 -1
  2. kstlib/__main__.py +16 -0
  3. kstlib/alerts/__init__.py +110 -0
  4. kstlib/alerts/channels/__init__.py +36 -0
  5. kstlib/alerts/channels/base.py +197 -0
  6. kstlib/alerts/channels/email.py +227 -0
  7. kstlib/alerts/channels/slack.py +389 -0
  8. kstlib/alerts/exceptions.py +72 -0
  9. kstlib/alerts/manager.py +651 -0
  10. kstlib/alerts/models.py +142 -0
  11. kstlib/alerts/throttle.py +263 -0
  12. kstlib/auth/__init__.py +139 -0
  13. kstlib/auth/callback.py +399 -0
  14. kstlib/auth/config.py +502 -0
  15. kstlib/auth/errors.py +127 -0
  16. kstlib/auth/models.py +316 -0
  17. kstlib/auth/providers/__init__.py +14 -0
  18. kstlib/auth/providers/base.py +393 -0
  19. kstlib/auth/providers/oauth2.py +645 -0
  20. kstlib/auth/providers/oidc.py +821 -0
  21. kstlib/auth/session.py +338 -0
  22. kstlib/auth/token.py +482 -0
  23. kstlib/cache/__init__.py +50 -0
  24. kstlib/cache/decorator.py +261 -0
  25. kstlib/cache/strategies.py +516 -0
  26. kstlib/cli/__init__.py +8 -0
  27. kstlib/cli/app.py +195 -0
  28. kstlib/cli/commands/__init__.py +5 -0
  29. kstlib/cli/commands/auth/__init__.py +39 -0
  30. kstlib/cli/commands/auth/common.py +122 -0
  31. kstlib/cli/commands/auth/login.py +325 -0
  32. kstlib/cli/commands/auth/logout.py +74 -0
  33. kstlib/cli/commands/auth/providers.py +57 -0
  34. kstlib/cli/commands/auth/status.py +291 -0
  35. kstlib/cli/commands/auth/token.py +199 -0
  36. kstlib/cli/commands/auth/whoami.py +106 -0
  37. kstlib/cli/commands/config.py +89 -0
  38. kstlib/cli/commands/ops/__init__.py +39 -0
  39. kstlib/cli/commands/ops/attach.py +49 -0
  40. kstlib/cli/commands/ops/common.py +269 -0
  41. kstlib/cli/commands/ops/list_sessions.py +252 -0
  42. kstlib/cli/commands/ops/logs.py +49 -0
  43. kstlib/cli/commands/ops/start.py +98 -0
  44. kstlib/cli/commands/ops/status.py +138 -0
  45. kstlib/cli/commands/ops/stop.py +60 -0
  46. kstlib/cli/commands/rapi/__init__.py +60 -0
  47. kstlib/cli/commands/rapi/call.py +341 -0
  48. kstlib/cli/commands/rapi/list.py +99 -0
  49. kstlib/cli/commands/rapi/show.py +206 -0
  50. kstlib/cli/commands/secrets/__init__.py +35 -0
  51. kstlib/cli/commands/secrets/common.py +425 -0
  52. kstlib/cli/commands/secrets/decrypt.py +88 -0
  53. kstlib/cli/commands/secrets/doctor.py +743 -0
  54. kstlib/cli/commands/secrets/encrypt.py +242 -0
  55. kstlib/cli/commands/secrets/shred.py +96 -0
  56. kstlib/cli/common.py +86 -0
  57. kstlib/config/__init__.py +76 -0
  58. kstlib/config/exceptions.py +110 -0
  59. kstlib/config/export.py +225 -0
  60. kstlib/config/loader.py +963 -0
  61. kstlib/config/sops.py +287 -0
  62. kstlib/db/__init__.py +54 -0
  63. kstlib/db/aiosqlcipher.py +137 -0
  64. kstlib/db/cipher.py +112 -0
  65. kstlib/db/database.py +367 -0
  66. kstlib/db/exceptions.py +25 -0
  67. kstlib/db/pool.py +302 -0
  68. kstlib/helpers/__init__.py +35 -0
  69. kstlib/helpers/exceptions.py +11 -0
  70. kstlib/helpers/time_trigger.py +396 -0
  71. kstlib/kstlib.conf.yml +890 -0
  72. kstlib/limits.py +963 -0
  73. kstlib/logging/__init__.py +108 -0
  74. kstlib/logging/manager.py +633 -0
  75. kstlib/mail/__init__.py +42 -0
  76. kstlib/mail/builder.py +626 -0
  77. kstlib/mail/exceptions.py +27 -0
  78. kstlib/mail/filesystem.py +248 -0
  79. kstlib/mail/transport.py +224 -0
  80. kstlib/mail/transports/__init__.py +19 -0
  81. kstlib/mail/transports/gmail.py +268 -0
  82. kstlib/mail/transports/resend.py +324 -0
  83. kstlib/mail/transports/smtp.py +326 -0
  84. kstlib/meta.py +72 -0
  85. kstlib/metrics/__init__.py +88 -0
  86. kstlib/metrics/decorators.py +1090 -0
  87. kstlib/metrics/exceptions.py +14 -0
  88. kstlib/monitoring/__init__.py +116 -0
  89. kstlib/monitoring/_styles.py +163 -0
  90. kstlib/monitoring/cell.py +57 -0
  91. kstlib/monitoring/config.py +424 -0
  92. kstlib/monitoring/delivery.py +579 -0
  93. kstlib/monitoring/exceptions.py +63 -0
  94. kstlib/monitoring/image.py +220 -0
  95. kstlib/monitoring/kv.py +79 -0
  96. kstlib/monitoring/list.py +69 -0
  97. kstlib/monitoring/metric.py +88 -0
  98. kstlib/monitoring/monitoring.py +341 -0
  99. kstlib/monitoring/renderer.py +139 -0
  100. kstlib/monitoring/service.py +392 -0
  101. kstlib/monitoring/table.py +129 -0
  102. kstlib/monitoring/types.py +56 -0
  103. kstlib/ops/__init__.py +86 -0
  104. kstlib/ops/base.py +148 -0
  105. kstlib/ops/container.py +577 -0
  106. kstlib/ops/exceptions.py +209 -0
  107. kstlib/ops/manager.py +407 -0
  108. kstlib/ops/models.py +176 -0
  109. kstlib/ops/tmux.py +372 -0
  110. kstlib/ops/validators.py +287 -0
  111. kstlib/py.typed +0 -0
  112. kstlib/rapi/__init__.py +118 -0
  113. kstlib/rapi/client.py +875 -0
  114. kstlib/rapi/config.py +861 -0
  115. kstlib/rapi/credentials.py +887 -0
  116. kstlib/rapi/exceptions.py +213 -0
  117. kstlib/resilience/__init__.py +101 -0
  118. kstlib/resilience/circuit_breaker.py +440 -0
  119. kstlib/resilience/exceptions.py +95 -0
  120. kstlib/resilience/heartbeat.py +491 -0
  121. kstlib/resilience/rate_limiter.py +506 -0
  122. kstlib/resilience/shutdown.py +417 -0
  123. kstlib/resilience/watchdog.py +637 -0
  124. kstlib/secrets/__init__.py +29 -0
  125. kstlib/secrets/exceptions.py +19 -0
  126. kstlib/secrets/models.py +62 -0
  127. kstlib/secrets/providers/__init__.py +79 -0
  128. kstlib/secrets/providers/base.py +58 -0
  129. kstlib/secrets/providers/environment.py +66 -0
  130. kstlib/secrets/providers/keyring.py +107 -0
  131. kstlib/secrets/providers/kms.py +223 -0
  132. kstlib/secrets/providers/kwargs.py +101 -0
  133. kstlib/secrets/providers/sops.py +209 -0
  134. kstlib/secrets/resolver.py +221 -0
  135. kstlib/secrets/sensitive.py +130 -0
  136. kstlib/secure/__init__.py +23 -0
  137. kstlib/secure/fs.py +194 -0
  138. kstlib/secure/permissions.py +70 -0
  139. kstlib/ssl.py +347 -0
  140. kstlib/ui/__init__.py +23 -0
  141. kstlib/ui/exceptions.py +26 -0
  142. kstlib/ui/panels.py +484 -0
  143. kstlib/ui/spinner.py +864 -0
  144. kstlib/ui/tables.py +382 -0
  145. kstlib/utils/__init__.py +48 -0
  146. kstlib/utils/dict.py +36 -0
  147. kstlib/utils/formatting.py +338 -0
  148. kstlib/utils/http_trace.py +237 -0
  149. kstlib/utils/lazy.py +49 -0
  150. kstlib/utils/secure_delete.py +205 -0
  151. kstlib/utils/serialization.py +247 -0
  152. kstlib/utils/text.py +56 -0
  153. kstlib/utils/validators.py +124 -0
  154. kstlib/websocket/__init__.py +97 -0
  155. kstlib/websocket/exceptions.py +214 -0
  156. kstlib/websocket/manager.py +1102 -0
  157. kstlib/websocket/models.py +361 -0
  158. kstlib-1.0.1.dist-info/METADATA +201 -0
  159. kstlib-1.0.1.dist-info/RECORD +163 -0
  160. {kstlib-0.0.1a0.dist-info → kstlib-1.0.1.dist-info}/WHEEL +1 -1
  161. kstlib-1.0.1.dist-info/entry_points.txt +2 -0
  162. kstlib-1.0.1.dist-info/licenses/LICENSE.md +9 -0
  163. kstlib-0.0.1a0.dist-info/METADATA +0 -29
  164. kstlib-0.0.1a0.dist-info/RECORD +0 -6
  165. kstlib-0.0.1a0.dist-info/licenses/LICENSE.md +0 -5
  166. {kstlib-0.0.1a0.dist-info → kstlib-1.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,361 @@
1
+ """WebSocket data models and enumerations.
2
+
3
+ This module provides the core data structures for the WebSocket manager:
4
+
5
+ - **ConnectionState**: State machine for connection lifecycle
6
+ - **DisconnectReason**: Categorizes disconnection causes (proactive vs reactive)
7
+ - **ReconnectStrategy**: Available reconnection strategies
8
+ - **WebSocketStats**: Connection and message statistics
9
+
10
+ Examples:
11
+ >>> from kstlib.websocket.models import ConnectionState, DisconnectReason
12
+ >>> state = ConnectionState.CONNECTED
13
+ >>> reason = DisconnectReason.USER_REQUESTED
14
+ >>> reason.is_proactive
15
+ True
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import time
21
+ from dataclasses import dataclass, field
22
+ from enum import Enum, auto
23
+
24
+ __all__ = [
25
+ "ConnectionState",
26
+ "DisconnectReason",
27
+ "ReconnectStrategy",
28
+ "WebSocketStats",
29
+ ]
30
+
31
+
32
+ class ConnectionState(Enum):
33
+ """WebSocket connection state machine.
34
+
35
+ State transitions:
36
+ DISCONNECTED -> CONNECTING -> CONNECTED
37
+ CONNECTED -> RECONNECTING -> CONNECTED (on success)
38
+ CONNECTED -> RECONNECTING -> DISCONNECTED (on failure)
39
+ CONNECTED -> CLOSING -> CLOSED
40
+ Any state -> CLOSED (on force_close)
41
+
42
+ Attributes:
43
+ DISCONNECTED: Initial state, not connected.
44
+ CONNECTING: Connection attempt in progress.
45
+ CONNECTED: WebSocket connection is active.
46
+ RECONNECTING: Attempting to restore lost connection.
47
+ CLOSING: Graceful shutdown in progress.
48
+ CLOSED: Terminal state, cannot reconnect.
49
+ """
50
+
51
+ DISCONNECTED = auto()
52
+ CONNECTING = auto()
53
+ CONNECTED = auto()
54
+ RECONNECTING = auto()
55
+ CLOSING = auto()
56
+ CLOSED = auto()
57
+
58
+ def can_connect(self) -> bool:
59
+ """Check if a connection attempt is allowed from this state.
60
+
61
+ Returns:
62
+ True if connect() can be called from this state.
63
+
64
+ Examples:
65
+ >>> ConnectionState.DISCONNECTED.can_connect()
66
+ True
67
+ >>> ConnectionState.CONNECTED.can_connect()
68
+ False
69
+ """
70
+ return self in (ConnectionState.DISCONNECTED, ConnectionState.RECONNECTING)
71
+
72
+ def can_send(self) -> bool:
73
+ """Check if sending messages is allowed from this state.
74
+
75
+ Returns:
76
+ True if send() can be called from this state.
77
+
78
+ Examples:
79
+ >>> ConnectionState.CONNECTED.can_send()
80
+ True
81
+ >>> ConnectionState.DISCONNECTED.can_send()
82
+ False
83
+ """
84
+ return self == ConnectionState.CONNECTED
85
+
86
+ def is_terminal(self) -> bool:
87
+ """Check if this is a terminal state.
88
+
89
+ Returns:
90
+ True if no further state transitions are possible.
91
+
92
+ Examples:
93
+ >>> ConnectionState.CLOSED.is_terminal()
94
+ True
95
+ >>> ConnectionState.DISCONNECTED.is_terminal()
96
+ False
97
+ """
98
+ return self == ConnectionState.CLOSED
99
+
100
+
101
+ class DisconnectReason(Enum):
102
+ """Reason for WebSocket disconnection.
103
+
104
+ Disconnections are categorized as either proactive (user-controlled)
105
+ or reactive (forced by external factors). This distinction is key
106
+ for the proactive connection control feature.
107
+
108
+ Proactive reasons (user-controlled):
109
+ USER_REQUESTED: Manual disconnect via request_disconnect()
110
+ SCHEDULED: Disconnect triggered by schedule_reconnect()
111
+ CALLBACK_TRIGGERED: should_disconnect() callback returned True
112
+ CONNECTION_LIMIT: Preemptive disconnect before platform limit
113
+
114
+ Reactive reasons (forced):
115
+ SERVER_CLOSED: Server initiated the close
116
+ NETWORK_ERROR: Network connectivity issue
117
+ PING_TIMEOUT: No pong response within timeout
118
+ PROTOCOL_ERROR: WebSocket protocol violation
119
+ """
120
+
121
+ # Proactive (user-controlled) disconnections
122
+ USER_REQUESTED = auto()
123
+ SCHEDULED = auto()
124
+ CALLBACK_TRIGGERED = auto()
125
+ CONNECTION_LIMIT = auto()
126
+
127
+ # Reactive (forced) disconnections
128
+ SERVER_CLOSED = auto()
129
+ NETWORK_ERROR = auto()
130
+ PING_TIMEOUT = auto()
131
+ PROTOCOL_ERROR = auto()
132
+ KILLED = auto() # Simulated external kill (e.g., Binance forced disconnect)
133
+
134
+ @property
135
+ def is_proactive(self) -> bool:
136
+ """Check if this is a proactive (user-controlled) disconnection.
137
+
138
+ Returns:
139
+ True if the disconnection was initiated by the user/application.
140
+
141
+ Examples:
142
+ >>> DisconnectReason.USER_REQUESTED.is_proactive
143
+ True
144
+ >>> DisconnectReason.NETWORK_ERROR.is_proactive
145
+ False
146
+ """
147
+ return self in (
148
+ DisconnectReason.USER_REQUESTED,
149
+ DisconnectReason.SCHEDULED,
150
+ DisconnectReason.CALLBACK_TRIGGERED,
151
+ DisconnectReason.CONNECTION_LIMIT,
152
+ )
153
+
154
+ @property
155
+ def is_reactive(self) -> bool:
156
+ """Check if this is a reactive (forced) disconnection.
157
+
158
+ Returns:
159
+ True if the disconnection was forced by external factors.
160
+
161
+ Examples:
162
+ >>> DisconnectReason.SERVER_CLOSED.is_reactive
163
+ True
164
+ >>> DisconnectReason.USER_REQUESTED.is_reactive
165
+ False
166
+ """
167
+ return not self.is_proactive
168
+
169
+
170
+ class ReconnectStrategy(Enum):
171
+ """Reconnection strategy after disconnection.
172
+
173
+ Attributes:
174
+ IMMEDIATE: Reconnect immediately without delay.
175
+ FIXED_DELAY: Wait a fixed delay before each attempt.
176
+ EXPONENTIAL_BACKOFF: Exponentially increasing delays.
177
+ CALLBACK_CONTROLLED: Reconnection timing controlled by callback.
178
+ """
179
+
180
+ IMMEDIATE = auto()
181
+ FIXED_DELAY = auto()
182
+ EXPONENTIAL_BACKOFF = auto()
183
+ CALLBACK_CONTROLLED = auto()
184
+
185
+
186
+ @dataclass
187
+ class WebSocketStats:
188
+ """WebSocket connection and message statistics.
189
+
190
+ Tracks both connection lifecycle events and message throughput.
191
+ The key distinction is between proactive and reactive disconnections,
192
+ which is central to the proactive control feature.
193
+
194
+ Attributes:
195
+ connects: Total successful connection count.
196
+ disconnects: Total disconnection count.
197
+ proactive_disconnects: Disconnections initiated by user/application.
198
+ reactive_disconnects: Disconnections forced by external factors.
199
+ messages_received: Total messages received.
200
+ messages_sent: Total messages sent.
201
+ bytes_received: Total bytes received.
202
+ bytes_sent: Total bytes sent.
203
+ last_connect_time: Unix timestamp of last successful connection.
204
+ last_disconnect_time: Unix timestamp of last disconnection.
205
+ last_message_time: Unix timestamp of last message (sent or received).
206
+
207
+ Examples:
208
+ >>> stats = WebSocketStats()
209
+ >>> stats.record_connect()
210
+ >>> stats.connects
211
+ 1
212
+ >>> stats.record_disconnect(proactive=True)
213
+ >>> stats.proactive_disconnects
214
+ 1
215
+ """
216
+
217
+ connects: int = 0
218
+ disconnects: int = 0
219
+ proactive_disconnects: int = 0
220
+ reactive_disconnects: int = 0
221
+ messages_received: int = 0
222
+ messages_sent: int = 0
223
+ bytes_received: int = 0
224
+ bytes_sent: int = 0
225
+ last_connect_time: float = 0.0
226
+ last_disconnect_time: float = 0.0
227
+ last_message_time: float = 0.0
228
+ _start_time: float = field(default_factory=time.monotonic)
229
+
230
+ def record_connect(self) -> None:
231
+ """Record a successful connection.
232
+
233
+ Examples:
234
+ >>> stats = WebSocketStats()
235
+ >>> stats.record_connect()
236
+ >>> stats.connects
237
+ 1
238
+ """
239
+ self.connects += 1
240
+ self.last_connect_time = time.time()
241
+
242
+ def record_disconnect(self, *, proactive: bool = False) -> None:
243
+ """Record a disconnection.
244
+
245
+ Args:
246
+ proactive: True if this was a user-initiated disconnection.
247
+
248
+ Examples:
249
+ >>> stats = WebSocketStats()
250
+ >>> stats.record_disconnect(proactive=True)
251
+ >>> stats.proactive_disconnects
252
+ 1
253
+ >>> stats.record_disconnect(proactive=False)
254
+ >>> stats.reactive_disconnects
255
+ 1
256
+ """
257
+ self.disconnects += 1
258
+ self.last_disconnect_time = time.time()
259
+ if proactive:
260
+ self.proactive_disconnects += 1
261
+ else:
262
+ self.reactive_disconnects += 1
263
+
264
+ def record_message_received(self, size: int = 0) -> None:
265
+ """Record a received message.
266
+
267
+ Args:
268
+ size: Size of the message in bytes.
269
+
270
+ Examples:
271
+ >>> stats = WebSocketStats()
272
+ >>> stats.record_message_received(100)
273
+ >>> stats.messages_received
274
+ 1
275
+ >>> stats.bytes_received
276
+ 100
277
+ """
278
+ self.messages_received += 1
279
+ self.bytes_received += size
280
+ self.last_message_time = time.time()
281
+
282
+ def record_message_sent(self, size: int = 0) -> None:
283
+ """Record a sent message.
284
+
285
+ Args:
286
+ size: Size of the message in bytes.
287
+
288
+ Examples:
289
+ >>> stats = WebSocketStats()
290
+ >>> stats.record_message_sent(50)
291
+ >>> stats.messages_sent
292
+ 1
293
+ >>> stats.bytes_sent
294
+ 50
295
+ """
296
+ self.messages_sent += 1
297
+ self.bytes_sent += size
298
+ self.last_message_time = time.time()
299
+
300
+ @property
301
+ def uptime(self) -> float:
302
+ """Time since stats object was created, in seconds.
303
+
304
+ Returns:
305
+ Elapsed time in seconds.
306
+
307
+ Examples:
308
+ >>> import time
309
+ >>> stats = WebSocketStats()
310
+ >>> time.sleep(0.05)
311
+ >>> stats.uptime > 0
312
+ True
313
+ """
314
+ return time.monotonic() - self._start_time
315
+
316
+ @property
317
+ def connection_time(self) -> float:
318
+ """Time since last connection, in seconds.
319
+
320
+ Returns zero if never connected.
321
+
322
+ Returns:
323
+ Elapsed time since last connect, or 0 if never connected.
324
+
325
+ Examples:
326
+ >>> stats = WebSocketStats()
327
+ >>> stats.connection_time
328
+ 0.0
329
+ >>> stats.record_connect()
330
+ >>> stats.connection_time > 0 or stats.connection_time == 0.0
331
+ True
332
+ """
333
+ if self.last_connect_time == 0.0:
334
+ return 0.0
335
+ return time.time() - self.last_connect_time
336
+
337
+ def reset(self) -> None:
338
+ """Reset all statistics to zero.
339
+
340
+ Examples:
341
+ >>> stats = WebSocketStats()
342
+ >>> stats.record_connect()
343
+ >>> stats.record_message_sent(100)
344
+ >>> stats.reset()
345
+ >>> stats.connects
346
+ 0
347
+ >>> stats.messages_sent
348
+ 0
349
+ """
350
+ self.connects = 0
351
+ self.disconnects = 0
352
+ self.proactive_disconnects = 0
353
+ self.reactive_disconnects = 0
354
+ self.messages_received = 0
355
+ self.messages_sent = 0
356
+ self.bytes_received = 0
357
+ self.bytes_sent = 0
358
+ self.last_connect_time = 0.0
359
+ self.last_disconnect_time = 0.0
360
+ self.last_message_time = 0.0
361
+ self._start_time = time.monotonic()
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: kstlib
3
+ Version: 1.0.1
4
+ Summary: Config-driven helpers for Python projects (dynamic config, secure secrets, preset logging, and more…)
5
+ Author-email: Michel TRUONG <michel.truong@gmail.com>
6
+ Maintainer-email: Michel TRUONG <michel.truong@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/KaminoU/kstlib
9
+ Project-URL: Repository, https://github.com/KaminoU/kstlib
10
+ Project-URL: Bug Tracker, https://github.com/KaminoU/kstlib/issues
11
+ Project-URL: Changelog, https://github.com/KaminoU/kstlib/blob/main/CHANGELOG.md
12
+ Keywords: kstlib
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE.md
24
+ Requires-Dist: pyyaml<7,>=6.0
25
+ Requires-Dist: tomli<3,>=2.3
26
+ Requires-Dist: tomli-w<2,>=1.0
27
+ Requires-Dist: python-box<8,>=7.3
28
+ Requires-Dist: typer<1,>=0.19
29
+ Requires-Dist: click<9,>=8.3
30
+ Requires-Dist: rich<15,>=14.2
31
+ Requires-Dist: structlog<26,>=25.0
32
+ Requires-Dist: aiosqlite<1,>=0.21
33
+ Requires-Dist: aiosmtplib<5,>=4.0
34
+ Requires-Dist: websockets<16,>=15.0
35
+ Requires-Dist: jinja2<4,>=3.1
36
+ Requires-Dist: humanize<5,>=4.11
37
+ Requires-Dist: httpx<1,>=0.28
38
+ Requires-Dist: authlib<2,>=1.5
39
+ Requires-Dist: pendulum<4,>=3.0
40
+ Provides-Extra: dev
41
+ Requires-Dist: pytest<9,>=8.4; extra == "dev"
42
+ Requires-Dist: pytest-cov<8,>=7.0; extra == "dev"
43
+ Requires-Dist: pytest-asyncio<2,>=1.2; extra == "dev"
44
+ Requires-Dist: ruff<1,>=0.14; extra == "dev"
45
+ Requires-Dist: mypy<2,>=1.18; extra == "dev"
46
+ Requires-Dist: types-PyYAML<7,>=6.0; extra == "dev"
47
+ Requires-Dist: pre-commit<5,>=4.0; extra == "dev"
48
+ Requires-Dist: boto3<2,>=1.35; extra == "dev"
49
+ Provides-Extra: db-crypto
50
+ Requires-Dist: sqlcipher3<1,>=0.5; extra == "db-crypto"
51
+ Provides-Extra: docs
52
+ Requires-Dist: sphinx<9,>=8.1; extra == "docs"
53
+ Requires-Dist: furo<2026,>=2025.9; extra == "docs"
54
+ Requires-Dist: myst-parser<5,>=4.0; extra == "docs"
55
+ Requires-Dist: sphinx-autodoc-typehints<4,>=3.0; extra == "docs"
56
+ Requires-Dist: sphinx-togglebutton<1,>=0.3; extra == "docs"
57
+ Requires-Dist: sphinx-design<0.7,>=0.6; extra == "docs"
58
+ Provides-Extra: build
59
+ Requires-Dist: build<2,>=1.3; extra == "build"
60
+ Requires-Dist: twine<7,>=6.2; extra == "build"
61
+ Provides-Extra: tox
62
+ Requires-Dist: tox<5,>=4.31; extra == "tox"
63
+ Provides-Extra: infra-tools
64
+ Requires-Dist: awscli-local<1,>=0.22; extra == "infra-tools"
65
+ Provides-Extra: textual
66
+ Requires-Dist: textual<7,>=6.3; extra == "textual"
67
+ Provides-Extra: all
68
+ Requires-Dist: pytest<9,>=8.4; extra == "all"
69
+ Requires-Dist: pytest-cov<8,>=7.0; extra == "all"
70
+ Requires-Dist: pytest-asyncio<2,>=1.2; extra == "all"
71
+ Requires-Dist: ruff<1,>=0.14; extra == "all"
72
+ Requires-Dist: mypy<2,>=1.18; extra == "all"
73
+ Requires-Dist: types-PyYAML<7,>=6.0; extra == "all"
74
+ Requires-Dist: sphinx<9,>=8.1; extra == "all"
75
+ Requires-Dist: furo<2026,>=2025.9; extra == "all"
76
+ Requires-Dist: myst-parser<5,>=4.0; extra == "all"
77
+ Requires-Dist: sphinx-autodoc-typehints<4,>=3.0; extra == "all"
78
+ Requires-Dist: sphinx-togglebutton<1,>=0.3; extra == "all"
79
+ Requires-Dist: sphinx-design<0.7,>=0.6; extra == "all"
80
+ Requires-Dist: build<2,>=1.3; extra == "all"
81
+ Requires-Dist: twine<7,>=6.2; extra == "all"
82
+ Requires-Dist: tox<5,>=4.31; extra == "all"
83
+ Requires-Dist: pre-commit<5,>=4.0; extra == "all"
84
+ Requires-Dist: boto3<2,>=1.35; extra == "all"
85
+ Requires-Dist: awscli-local<1,>=0.22; extra == "all"
86
+ Dynamic: license-file
87
+
88
+ <p align="center">
89
+ <img src="https://raw.githubusercontent.com/KaminoU/kstlib/main/assets/kstlib.svg" alt="Kstlib Logo" width="420">
90
+ </p>
91
+
92
+ <p align="center">
93
+ <strong>Config-driven Python toolkit for resilient applications</strong>
94
+ </p>
95
+
96
+ <p align="center">
97
+ <a href="https://github.com/KaminoU/kstlib/actions/workflows/ci.yml"><img src="https://github.com/KaminoU/kstlib/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
98
+ <a href="https://kstlib.readthedocs.io/"><img src="https://img.shields.io/badge/docs-RTD-blue" alt="Documentation"></a>
99
+ <a href="https://pypi.org/project/kstlib/"><img src="https://img.shields.io/pypi/v/kstlib?color=blue" alt="PyPI"></a>
100
+ <img src="https://img.shields.io/badge/python-≥3.10-blue" alt="Python">
101
+ <a href="https://github.com/KaminoU/kstlib/blob/main/LICENSE.md"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a>
102
+ </p>
103
+
104
+ ---
105
+
106
+ **kstlib** is a personal Python toolkit built over 7 years of learning and experimentation.
107
+
108
+ It started as a way to explore Python best practices, evolved into utilities for personal automation,
109
+ and now serves as the foundation for study projects in algorithmic trading and market analysis.
110
+
111
+ The focus has always been on building **resilient, secure, and performant** systems.
112
+
113
+ > **Note**: Everything works via Python, but since kstlib is heavily config-driven,
114
+ > the [Examples Gallery](https://kstlib.readthedocs.io/en/latest/examples.html) showcases
115
+ > a YAML-first approach.
116
+
117
+ ## Core Modules
118
+
119
+ | Module | Purpose |
120
+ |--------|---------|
121
+ | **config** | Cascading config files, includes, SOPS encryption, Box access |
122
+ | **secrets** | Multi-provider resolver (env, keyring, SOPS, KMS) with guardrails |
123
+ | **logging** | Rich console, rotating files, TRACE level, structlog integration |
124
+ | **auth** | OIDC/OAuth2 with PKCE, token storage, auto-refresh |
125
+ | **mail** | Jinja templates, transports (SMTP, Gmail API, Resend) |
126
+ | **alerts** | Multi-channel (Slack, Email), throttling, severity levels |
127
+ | **websocket** | Resilient connections, auto-reconnect, heartbeat, watchdog |
128
+ | **rapi** | Config-driven REST client with HMAC signing |
129
+ | **monitoring** | Collectors + Jinja rendering + delivery (file, mail) |
130
+ | **resilience** | Circuit breaker, rate limiter, graceful shutdown |
131
+ | **ops** | Session manager (tmux), containers (Docker/Podman) |
132
+ | **helpers** | TimeTrigger, formatting, secure delete, validators |
133
+
134
+ ## Quick Start
135
+
136
+ ### Installation
137
+
138
+ ```bash
139
+ pip install kstlib
140
+ ```
141
+
142
+ ### Basic Usage
143
+
144
+ ```python
145
+ from kstlib.config import load_from_file
146
+ from kstlib import cache
147
+
148
+ config = load_from_file("config.yml")
149
+
150
+ @cache(ttl=300)
151
+ def expensive_computation(x: int) -> int:
152
+ return x ** 2
153
+
154
+ result = expensive_computation(5)
155
+ ```
156
+
157
+ ### Minimal Configuration
158
+
159
+ ```yaml
160
+ app:
161
+ name: "My Application"
162
+ debug: true
163
+
164
+ database:
165
+ host: "localhost"
166
+ port: 5432
167
+ ```
168
+
169
+ ## Documentation
170
+
171
+ Full documentation available at **[kstlib.readthedocs.io](https://kstlib.readthedocs.io/)**
172
+
173
+ - [Features Guide](https://kstlib.readthedocs.io/en/latest/features/index.html)
174
+ - [Examples Gallery](https://kstlib.readthedocs.io/en/latest/examples.html)
175
+ - [API Reference](https://kstlib.readthedocs.io/en/latest/api/index.html)
176
+ - [Development Guide](https://kstlib.readthedocs.io/en/latest/development/index.html)
177
+
178
+ ## Installation Options
179
+
180
+ ```bash
181
+ # Standard install
182
+ pip install kstlib
183
+
184
+ # With uv (faster)
185
+ uv pip install kstlib
186
+
187
+ # Development install
188
+ pip install "kstlib[dev]"
189
+
190
+ # All extras
191
+ pip install "kstlib[all]"
192
+
193
+ # From GitHub (latest)
194
+ pip install "git+https://github.com/KaminoU/kstlib.git"
195
+ ```
196
+
197
+ ## License
198
+
199
+ MIT License - Copyright 2025 Michel TRUONG
200
+
201
+ See [LICENSE](LICENSE.md) for full text.