rocket-welder-sdk 1.1.36.dev14__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.
- rocket_welder_sdk/__init__.py +95 -0
- rocket_welder_sdk/bytes_size.py +234 -0
- rocket_welder_sdk/connection_string.py +291 -0
- rocket_welder_sdk/controllers.py +831 -0
- rocket_welder_sdk/external_controls/__init__.py +30 -0
- rocket_welder_sdk/external_controls/contracts.py +100 -0
- rocket_welder_sdk/external_controls/contracts_old.py +105 -0
- rocket_welder_sdk/frame_metadata.py +138 -0
- rocket_welder_sdk/gst_metadata.py +411 -0
- rocket_welder_sdk/high_level/__init__.py +54 -0
- rocket_welder_sdk/high_level/client.py +235 -0
- rocket_welder_sdk/high_level/connection_strings.py +331 -0
- rocket_welder_sdk/high_level/data_context.py +169 -0
- rocket_welder_sdk/high_level/frame_sink_factory.py +118 -0
- rocket_welder_sdk/high_level/schema.py +195 -0
- rocket_welder_sdk/high_level/transport_protocol.py +238 -0
- rocket_welder_sdk/keypoints_protocol.py +642 -0
- rocket_welder_sdk/opencv_controller.py +278 -0
- rocket_welder_sdk/periodic_timer.py +303 -0
- rocket_welder_sdk/py.typed +2 -0
- rocket_welder_sdk/rocket_welder_client.py +497 -0
- rocket_welder_sdk/segmentation_result.py +420 -0
- rocket_welder_sdk/session_id.py +238 -0
- rocket_welder_sdk/transport/__init__.py +31 -0
- rocket_welder_sdk/transport/frame_sink.py +122 -0
- rocket_welder_sdk/transport/frame_source.py +74 -0
- rocket_welder_sdk/transport/nng_transport.py +197 -0
- rocket_welder_sdk/transport/stream_transport.py +193 -0
- rocket_welder_sdk/transport/tcp_transport.py +154 -0
- rocket_welder_sdk/transport/unix_socket_transport.py +339 -0
- rocket_welder_sdk/ui/__init__.py +48 -0
- rocket_welder_sdk/ui/controls.py +362 -0
- rocket_welder_sdk/ui/icons.py +21628 -0
- rocket_welder_sdk/ui/ui_events_projection.py +226 -0
- rocket_welder_sdk/ui/ui_service.py +358 -0
- rocket_welder_sdk/ui/value_types.py +72 -0
- rocket_welder_sdk-1.1.36.dev14.dist-info/METADATA +845 -0
- rocket_welder_sdk-1.1.36.dev14.dist-info/RECORD +40 -0
- rocket_welder_sdk-1.1.36.dev14.dist-info/WHEEL +5 -0
- rocket_welder_sdk-1.1.36.dev14.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enterprise-grade RocketWelder client for video streaming.
|
|
3
|
+
Main entry point for the RocketWelder SDK.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import queue
|
|
10
|
+
import threading
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from .connection_string import ConnectionMode, ConnectionString, Protocol
|
|
16
|
+
from .controllers import DuplexShmController, IController, OneWayShmController
|
|
17
|
+
from .frame_metadata import FrameMetadata # noqa: TC001 - used at runtime in callbacks
|
|
18
|
+
from .opencv_controller import OpenCvController
|
|
19
|
+
from .session_id import (
|
|
20
|
+
get_configured_nng_urls,
|
|
21
|
+
get_nng_urls_from_env,
|
|
22
|
+
has_explicit_nng_urls,
|
|
23
|
+
)
|
|
24
|
+
from .transport.nng_transport import NngFrameSink
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
import numpy.typing as npt
|
|
28
|
+
|
|
29
|
+
from .gst_metadata import GstMetadata
|
|
30
|
+
|
|
31
|
+
# Use numpy array type for Mat - OpenCV Mat is essentially a numpy array
|
|
32
|
+
Mat = npt.NDArray[np.uint8]
|
|
33
|
+
else:
|
|
34
|
+
Mat = np.ndarray # type: ignore[misc]
|
|
35
|
+
|
|
36
|
+
# Module logger
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class RocketWelderClient:
|
|
41
|
+
"""
|
|
42
|
+
Main client for RocketWelder video streaming services.
|
|
43
|
+
|
|
44
|
+
Provides a unified interface for different connection types and protocols.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, connection: Union[str, ConnectionString]):
|
|
48
|
+
"""
|
|
49
|
+
Initialize the RocketWelder client.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
connection: Connection string or ConnectionString object
|
|
53
|
+
"""
|
|
54
|
+
if isinstance(connection, str):
|
|
55
|
+
self._connection = ConnectionString.parse(connection)
|
|
56
|
+
else:
|
|
57
|
+
self._connection = connection
|
|
58
|
+
|
|
59
|
+
self._controller: Optional[IController] = None
|
|
60
|
+
self._lock = threading.Lock()
|
|
61
|
+
|
|
62
|
+
# NNG publishers for streaming results (auto-created if SessionId env var is set)
|
|
63
|
+
self._nng_publishers: dict[str, NngFrameSink] = {}
|
|
64
|
+
|
|
65
|
+
# Preview support
|
|
66
|
+
self._preview_enabled = (
|
|
67
|
+
self._connection.parameters.get("preview", "false").lower() == "true"
|
|
68
|
+
)
|
|
69
|
+
self._preview_queue: queue.Queue[Optional[Mat]] = queue.Queue(maxsize=2) # type: ignore[valid-type] # Small buffer
|
|
70
|
+
self._preview_window_name = "RocketWelder Preview"
|
|
71
|
+
self._original_callback: Any = None
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def connection(self) -> ConnectionString:
|
|
75
|
+
"""Get the connection configuration."""
|
|
76
|
+
return self._connection
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def is_running(self) -> bool:
|
|
80
|
+
"""Check if the client is running."""
|
|
81
|
+
with self._lock:
|
|
82
|
+
return self._controller is not None and self._controller.is_running
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def nng_publishers(self) -> dict[str, NngFrameSink]:
|
|
86
|
+
"""Get NNG publishers for streaming results.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Dictionary with 'segmentation', 'keypoints', 'actions' keys.
|
|
90
|
+
Empty if SessionId env var was not set at startup.
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
client.nng_publishers["segmentation"].write_frame(seg_data)
|
|
94
|
+
"""
|
|
95
|
+
return self._nng_publishers
|
|
96
|
+
|
|
97
|
+
def _create_nng_publishers(self) -> None:
|
|
98
|
+
"""Create NNG publishers for result streaming.
|
|
99
|
+
|
|
100
|
+
URLs are read from environment variables (preferred) or derived from SessionId (fallback).
|
|
101
|
+
|
|
102
|
+
Priority:
|
|
103
|
+
1. Explicit URLs: SEGMENTATION_SINK_URL, KEYPOINTS_SINK_URL, ACTIONS_SINK_URL
|
|
104
|
+
2. Derived from SessionId environment variable (backwards compatibility)
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
urls = get_configured_nng_urls()
|
|
108
|
+
|
|
109
|
+
for name, url in urls.items():
|
|
110
|
+
sink = NngFrameSink.create_publisher(url)
|
|
111
|
+
self._nng_publishers[name] = sink
|
|
112
|
+
logger.info("NNG publisher ready: %s at %s", name, url)
|
|
113
|
+
|
|
114
|
+
# Log configuration summary
|
|
115
|
+
logger.info(
|
|
116
|
+
"NNG publishers configured: seg=%s, kp=%s, actions=%s",
|
|
117
|
+
urls.get("segmentation", "(not configured)"),
|
|
118
|
+
urls.get("keypoints", "(not configured)"),
|
|
119
|
+
urls.get("actions", "(not configured)"),
|
|
120
|
+
)
|
|
121
|
+
except ValueError as ex:
|
|
122
|
+
# No URLs configured - this is expected for containers that don't publish results
|
|
123
|
+
logger.debug("NNG publishers not configured: %s", ex)
|
|
124
|
+
except Exception as ex:
|
|
125
|
+
logger.warning("Failed to create NNG publishers: %s", ex)
|
|
126
|
+
# Don't fail start() - NNG is optional for backwards compatibility
|
|
127
|
+
|
|
128
|
+
def get_metadata(self) -> Optional[GstMetadata]:
|
|
129
|
+
"""
|
|
130
|
+
Get the current GStreamer metadata.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
GstMetadata or None if not available
|
|
134
|
+
"""
|
|
135
|
+
with self._lock:
|
|
136
|
+
if self._controller:
|
|
137
|
+
return self._controller.get_metadata()
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
def start(
|
|
141
|
+
self,
|
|
142
|
+
on_frame: Union[Callable[[Mat], None], Callable[[Mat, Mat], None]], # type: ignore[valid-type]
|
|
143
|
+
cancellation_token: Optional[threading.Event] = None,
|
|
144
|
+
) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Start receiving/processing video frames.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
on_frame: Callback for frame processing.
|
|
150
|
+
For one-way: (input_frame) -> None
|
|
151
|
+
For duplex: (input_frame, output_frame) -> None
|
|
152
|
+
cancellation_token: Optional cancellation token
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
RuntimeError: If already running
|
|
156
|
+
ValueError: If connection type is not supported
|
|
157
|
+
"""
|
|
158
|
+
with self._lock:
|
|
159
|
+
if self._controller and self._controller.is_running:
|
|
160
|
+
raise RuntimeError("Client is already running")
|
|
161
|
+
|
|
162
|
+
# Create appropriate controller based on connection
|
|
163
|
+
if self._connection.protocol == Protocol.SHM:
|
|
164
|
+
if self._connection.connection_mode == ConnectionMode.DUPLEX:
|
|
165
|
+
self._controller = DuplexShmController(self._connection)
|
|
166
|
+
else:
|
|
167
|
+
self._controller = OneWayShmController(self._connection)
|
|
168
|
+
elif self._connection.protocol == Protocol.FILE or bool(
|
|
169
|
+
self._connection.protocol & Protocol.MJPEG # type: ignore[operator]
|
|
170
|
+
):
|
|
171
|
+
self._controller = OpenCvController(self._connection)
|
|
172
|
+
else:
|
|
173
|
+
raise ValueError(f"Unsupported protocol: {self._connection.protocol}")
|
|
174
|
+
|
|
175
|
+
# Auto-create NNG publishers if URLs are configured
|
|
176
|
+
# (explicit URLs via SEGMENTATION_SINK_URL etc., or derived from SessionId)
|
|
177
|
+
if has_explicit_nng_urls():
|
|
178
|
+
self._create_nng_publishers()
|
|
179
|
+
else:
|
|
180
|
+
# Log that NNG is not configured (informational)
|
|
181
|
+
urls = get_nng_urls_from_env()
|
|
182
|
+
logger.info(
|
|
183
|
+
"NNG sink URLs not configured (this is normal if not publishing AI results). "
|
|
184
|
+
"seg=%s, kp=%s, actions=%s",
|
|
185
|
+
urls.get("segmentation") or "(not set)",
|
|
186
|
+
urls.get("keypoints") or "(not set)",
|
|
187
|
+
urls.get("actions") or "(not set)",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# If preview is enabled, wrap the callback to capture frames
|
|
191
|
+
if self._preview_enabled:
|
|
192
|
+
self._original_callback = on_frame
|
|
193
|
+
|
|
194
|
+
# Determine if duplex or one-way
|
|
195
|
+
if self._connection.connection_mode == ConnectionMode.DUPLEX:
|
|
196
|
+
|
|
197
|
+
def preview_wrapper_duplex(
|
|
198
|
+
metadata: FrameMetadata, input_frame: Mat, output_frame: Mat # type: ignore[valid-type]
|
|
199
|
+
) -> None:
|
|
200
|
+
# Call original callback (ignoring FrameMetadata for backwards compatibility)
|
|
201
|
+
on_frame(input_frame, output_frame) # type: ignore[call-arg]
|
|
202
|
+
# Queue the OUTPUT frame for preview
|
|
203
|
+
try:
|
|
204
|
+
self._preview_queue.put_nowait(output_frame.copy()) # type: ignore[attr-defined]
|
|
205
|
+
except queue.Full:
|
|
206
|
+
# Drop oldest frame if queue is full
|
|
207
|
+
try:
|
|
208
|
+
self._preview_queue.get_nowait()
|
|
209
|
+
self._preview_queue.put_nowait(output_frame.copy()) # type: ignore[attr-defined]
|
|
210
|
+
except queue.Empty:
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
actual_callback = preview_wrapper_duplex
|
|
214
|
+
else:
|
|
215
|
+
|
|
216
|
+
def preview_wrapper_oneway(frame: Mat) -> None: # type: ignore[valid-type]
|
|
217
|
+
# Call original callback
|
|
218
|
+
on_frame(frame) # type: ignore[call-arg]
|
|
219
|
+
# Queue frame for preview
|
|
220
|
+
try:
|
|
221
|
+
self._preview_queue.put_nowait(frame.copy()) # type: ignore[attr-defined]
|
|
222
|
+
except queue.Full:
|
|
223
|
+
# Drop oldest frame if queue is full
|
|
224
|
+
try:
|
|
225
|
+
self._preview_queue.get_nowait()
|
|
226
|
+
self._preview_queue.put_nowait(frame.copy()) # type: ignore[attr-defined]
|
|
227
|
+
except queue.Empty:
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
actual_callback = preview_wrapper_oneway # type: ignore[assignment]
|
|
231
|
+
else:
|
|
232
|
+
# Wrap the callback to adapt (Mat, Mat) -> (FrameMetadata, Mat, Mat) for duplex
|
|
233
|
+
if self._connection.connection_mode == ConnectionMode.DUPLEX:
|
|
234
|
+
|
|
235
|
+
def metadata_adapter(
|
|
236
|
+
metadata: FrameMetadata, input_frame: Mat, output_frame: Mat # type: ignore[valid-type]
|
|
237
|
+
) -> None:
|
|
238
|
+
# Call original callback (ignoring FrameMetadata for backwards compatibility)
|
|
239
|
+
on_frame(input_frame, output_frame) # type: ignore[call-arg]
|
|
240
|
+
|
|
241
|
+
actual_callback = metadata_adapter
|
|
242
|
+
else:
|
|
243
|
+
actual_callback = on_frame # type: ignore[assignment]
|
|
244
|
+
|
|
245
|
+
# Start the controller
|
|
246
|
+
self._controller.start(actual_callback, cancellation_token) # type: ignore[arg-type]
|
|
247
|
+
logger.info("RocketWelder client started with %s", self._connection)
|
|
248
|
+
|
|
249
|
+
def stop(self) -> None:
|
|
250
|
+
"""Stop the client and clean up resources."""
|
|
251
|
+
with self._lock:
|
|
252
|
+
if self._controller:
|
|
253
|
+
self._controller.stop()
|
|
254
|
+
self._controller = None
|
|
255
|
+
|
|
256
|
+
# Signal preview to stop if enabled
|
|
257
|
+
if self._preview_enabled:
|
|
258
|
+
self._preview_queue.put(None) # Sentinel value
|
|
259
|
+
|
|
260
|
+
# Clean up NNG publishers
|
|
261
|
+
for name, sink in self._nng_publishers.items():
|
|
262
|
+
try:
|
|
263
|
+
sink.close()
|
|
264
|
+
logger.debug("Closed NNG publisher: %s", name)
|
|
265
|
+
except Exception as ex:
|
|
266
|
+
logger.warning("Failed to close NNG publisher %s: %s", name, ex)
|
|
267
|
+
self._nng_publishers.clear()
|
|
268
|
+
|
|
269
|
+
logger.info("RocketWelder client stopped")
|
|
270
|
+
|
|
271
|
+
def show(self, cancellation_token: Optional[threading.Event] = None) -> None:
|
|
272
|
+
"""
|
|
273
|
+
Display preview frames in a window (main thread only).
|
|
274
|
+
|
|
275
|
+
This method should be called from the main thread after start().
|
|
276
|
+
- If preview=true: blocks and displays frames until stopped or 'q' pressed
|
|
277
|
+
- If preview=false or not set: returns immediately
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
cancellation_token: Optional cancellation token to stop preview
|
|
281
|
+
|
|
282
|
+
Example:
|
|
283
|
+
client = RocketWelderClient("file:///video.mp4?preview=true")
|
|
284
|
+
client.start(process_frame)
|
|
285
|
+
client.show() # Blocks and shows preview
|
|
286
|
+
client.stop()
|
|
287
|
+
"""
|
|
288
|
+
if not self._preview_enabled:
|
|
289
|
+
# No preview requested, return immediately
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
import cv2
|
|
294
|
+
except ImportError:
|
|
295
|
+
logger.warning("OpenCV not available, cannot show preview")
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
logger.info("Starting preview display in main thread")
|
|
299
|
+
|
|
300
|
+
# Create window
|
|
301
|
+
cv2.namedWindow(self._preview_window_name, cv2.WINDOW_NORMAL)
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
while True:
|
|
305
|
+
# Check for cancellation
|
|
306
|
+
if cancellation_token and cancellation_token.is_set():
|
|
307
|
+
break
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
# Get frame with timeout
|
|
311
|
+
frame = self._preview_queue.get(timeout=0.1)
|
|
312
|
+
|
|
313
|
+
# Check for stop sentinel
|
|
314
|
+
if frame is None:
|
|
315
|
+
break
|
|
316
|
+
|
|
317
|
+
# Display frame
|
|
318
|
+
cv2.imshow(self._preview_window_name, frame)
|
|
319
|
+
|
|
320
|
+
# Process window events and check for 'q' key
|
|
321
|
+
key = cv2.waitKey(1) & 0xFF
|
|
322
|
+
if key == ord("q"):
|
|
323
|
+
logger.info("User pressed 'q', stopping preview")
|
|
324
|
+
break
|
|
325
|
+
|
|
326
|
+
except queue.Empty:
|
|
327
|
+
# No frame available, check if still running
|
|
328
|
+
if not self.is_running:
|
|
329
|
+
break
|
|
330
|
+
# Process window events even without new frame
|
|
331
|
+
if cv2.waitKey(1) & 0xFF == ord("q"):
|
|
332
|
+
logger.info("User pressed 'q', stopping preview")
|
|
333
|
+
break
|
|
334
|
+
|
|
335
|
+
finally:
|
|
336
|
+
# Clean up window
|
|
337
|
+
cv2.destroyWindow(self._preview_window_name)
|
|
338
|
+
cv2.waitKey(1) # Process pending events
|
|
339
|
+
logger.info("Preview display stopped")
|
|
340
|
+
|
|
341
|
+
def __enter__(self) -> RocketWelderClient:
|
|
342
|
+
"""Context manager entry."""
|
|
343
|
+
return self
|
|
344
|
+
|
|
345
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
346
|
+
"""Context manager exit."""
|
|
347
|
+
self.stop()
|
|
348
|
+
|
|
349
|
+
@classmethod
|
|
350
|
+
def from_connection_string(cls, connection_string: str) -> RocketWelderClient:
|
|
351
|
+
"""
|
|
352
|
+
Create a client from a connection string.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
connection_string: Connection string (e.g., 'shm://buffer?mode=Duplex')
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Configured RocketWelderClient instance
|
|
359
|
+
"""
|
|
360
|
+
return cls(connection_string)
|
|
361
|
+
|
|
362
|
+
@classmethod
|
|
363
|
+
def from_args(cls, args: List[str]) -> RocketWelderClient:
|
|
364
|
+
"""
|
|
365
|
+
Create a client from command line arguments.
|
|
366
|
+
|
|
367
|
+
Checks in order:
|
|
368
|
+
1. First positional argument from args
|
|
369
|
+
2. CONNECTION_STRING environment variable
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
args: Command line arguments (typically sys.argv)
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Configured RocketWelderClient instance
|
|
376
|
+
|
|
377
|
+
Raises:
|
|
378
|
+
ValueError: If no connection string is found
|
|
379
|
+
"""
|
|
380
|
+
import os
|
|
381
|
+
|
|
382
|
+
# Check for positional argument (skip script name if present)
|
|
383
|
+
connection_string = None
|
|
384
|
+
for arg in args[1:] if len(args) > 0 and args[0].endswith(".py") else args:
|
|
385
|
+
if not arg.startswith("-"):
|
|
386
|
+
connection_string = arg
|
|
387
|
+
break
|
|
388
|
+
|
|
389
|
+
# Fall back to environment variable
|
|
390
|
+
if not connection_string:
|
|
391
|
+
connection_string = os.environ.get("CONNECTION_STRING")
|
|
392
|
+
|
|
393
|
+
if not connection_string:
|
|
394
|
+
raise ValueError(
|
|
395
|
+
"No connection string provided. "
|
|
396
|
+
"Provide as argument or set CONNECTION_STRING environment variable"
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
return cls(connection_string)
|
|
400
|
+
|
|
401
|
+
@classmethod
|
|
402
|
+
def from_(cls, *args: Any, **kwargs: Any) -> RocketWelderClient:
|
|
403
|
+
"""
|
|
404
|
+
Create a client with automatic configuration detection.
|
|
405
|
+
|
|
406
|
+
This is the most convenient factory method that:
|
|
407
|
+
1. Checks kwargs for 'args' parameter (command line arguments)
|
|
408
|
+
2. Checks args for command line arguments
|
|
409
|
+
3. Falls back to CONNECTION_STRING environment variable
|
|
410
|
+
|
|
411
|
+
Examples:
|
|
412
|
+
client = RocketWelderClient.from_() # Uses env var
|
|
413
|
+
client = RocketWelderClient.from_(sys.argv) # Uses command line
|
|
414
|
+
client = RocketWelderClient.from_(args=sys.argv) # Named param
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
Configured RocketWelderClient instance
|
|
418
|
+
|
|
419
|
+
Raises:
|
|
420
|
+
ValueError: If no connection string is found
|
|
421
|
+
"""
|
|
422
|
+
import os
|
|
423
|
+
|
|
424
|
+
# Check kwargs first
|
|
425
|
+
argv = kwargs.get("args")
|
|
426
|
+
|
|
427
|
+
# Then check positional args
|
|
428
|
+
if not argv and args:
|
|
429
|
+
# If first arg looks like sys.argv (list), use it
|
|
430
|
+
if isinstance(args[0], list):
|
|
431
|
+
argv = args[0]
|
|
432
|
+
# If first arg is a string, treat it as connection string
|
|
433
|
+
elif isinstance(args[0], str):
|
|
434
|
+
return cls(args[0])
|
|
435
|
+
|
|
436
|
+
# Try to get from command line args if provided
|
|
437
|
+
if argv:
|
|
438
|
+
try:
|
|
439
|
+
return cls.from_args(argv)
|
|
440
|
+
except ValueError:
|
|
441
|
+
pass # Fall through to env var check
|
|
442
|
+
|
|
443
|
+
# Fall back to environment variable
|
|
444
|
+
connection_string = os.environ.get("CONNECTION_STRING")
|
|
445
|
+
if connection_string:
|
|
446
|
+
return cls(connection_string)
|
|
447
|
+
|
|
448
|
+
raise ValueError(
|
|
449
|
+
"No connection string provided. "
|
|
450
|
+
"Provide as argument or set CONNECTION_STRING environment variable"
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
@classmethod
|
|
454
|
+
def create_oneway_shm(
|
|
455
|
+
cls,
|
|
456
|
+
buffer_name: str,
|
|
457
|
+
buffer_size: str = "256MB",
|
|
458
|
+
metadata_size: str = "4KB",
|
|
459
|
+
) -> RocketWelderClient:
|
|
460
|
+
"""
|
|
461
|
+
Create a one-way shared memory client.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
buffer_name: Name of the shared memory buffer
|
|
465
|
+
buffer_size: Size of the buffer (e.g., "256MB")
|
|
466
|
+
metadata_size: Size of metadata buffer (e.g., "4KB")
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Configured RocketWelderClient instance
|
|
470
|
+
"""
|
|
471
|
+
connection_str = (
|
|
472
|
+
f"shm://{buffer_name}?size={buffer_size}&metadata={metadata_size}&mode=OneWay"
|
|
473
|
+
)
|
|
474
|
+
return cls(connection_str)
|
|
475
|
+
|
|
476
|
+
@classmethod
|
|
477
|
+
def create_duplex_shm(
|
|
478
|
+
cls,
|
|
479
|
+
buffer_name: str,
|
|
480
|
+
buffer_size: str = "256MB",
|
|
481
|
+
metadata_size: str = "4KB",
|
|
482
|
+
) -> RocketWelderClient:
|
|
483
|
+
"""
|
|
484
|
+
Create a duplex shared memory client.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
buffer_name: Name of the shared memory buffer
|
|
488
|
+
buffer_size: Size of the buffer (e.g., "256MB")
|
|
489
|
+
metadata_size: Size of metadata buffer (e.g., "4KB")
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
Configured RocketWelderClient instance
|
|
493
|
+
"""
|
|
494
|
+
connection_str = (
|
|
495
|
+
f"shm://{buffer_name}?size={buffer_size}&metadata={metadata_size}&mode=Duplex"
|
|
496
|
+
)
|
|
497
|
+
return cls(connection_str)
|