rocket-welder-sdk 1.1.42__py3-none-any.whl → 1.1.44__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.
@@ -0,0 +1,213 @@
1
+ """
2
+ Core varint and ZigZag encoding/decoding algorithms.
3
+ Matches C# Varint static class from RocketWelder.SDK.Protocols.
4
+
5
+ Single source of truth for all varint operations in the SDK.
6
+ Compatible with Protocol Buffers varint encoding.
7
+
8
+ Varint encoding uses 7 bits per byte with MSB as continuation flag.
9
+ ZigZag encoding maps signed integers to unsigned for efficient varint encoding
10
+ of values near zero (both positive and negative).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Tuple
16
+
17
+ # Maximum bytes needed for a uint32 varint
18
+ MAX_BYTES_UINT32 = 5
19
+
20
+
21
+ def zigzag_encode(value: int) -> int:
22
+ """
23
+ ZigZag encode a signed integer to unsigned.
24
+
25
+ Maps negative numbers to odd positives: 0->0, -1->1, 1->2, -2->3, 2->4, etc.
26
+ This allows efficient varint encoding of signed values near zero.
27
+
28
+ Args:
29
+ value: Signed integer to encode
30
+
31
+ Returns:
32
+ Unsigned integer (ZigZag encoded)
33
+ """
34
+ return (value << 1) ^ (value >> 31)
35
+
36
+
37
+ def zigzag_decode(value: int) -> int:
38
+ """
39
+ ZigZag decode an unsigned integer to signed.
40
+
41
+ Reverses the ZigZag encoding: 0->0, 1->-1, 2->1, 3->-2, 4->2, etc.
42
+
43
+ Args:
44
+ value: Unsigned integer (ZigZag encoded)
45
+
46
+ Returns:
47
+ Signed integer (decoded)
48
+ """
49
+ return (value >> 1) ^ -(value & 1)
50
+
51
+
52
+ def get_byte_count(value: int) -> int:
53
+ """
54
+ Calculate the number of bytes needed to encode a value as varint.
55
+
56
+ Args:
57
+ value: Unsigned integer value
58
+
59
+ Returns:
60
+ Number of bytes needed (1-5)
61
+ """
62
+ if value < 0x80:
63
+ return 1
64
+ if value < 0x4000:
65
+ return 2
66
+ if value < 0x200000:
67
+ return 3
68
+ if value < 0x10000000:
69
+ return 4
70
+ return 5
71
+
72
+
73
+ def write_varint(buffer: bytearray, offset: int, value: int) -> int:
74
+ """
75
+ Write a varint to a buffer at the given offset.
76
+
77
+ Args:
78
+ buffer: Destination buffer (must have at least 5 bytes from offset)
79
+ offset: Starting offset in buffer
80
+ value: Unsigned value to encode
81
+
82
+ Returns:
83
+ Number of bytes written (1-5)
84
+ """
85
+ i = 0
86
+ while value >= 0x80:
87
+ buffer[offset + i] = (value & 0x7F) | 0x80
88
+ value >>= 7
89
+ i += 1
90
+ buffer[offset + i] = value
91
+ return i + 1
92
+
93
+
94
+ def write_zigzag(buffer: bytearray, offset: int, value: int) -> int:
95
+ """
96
+ Write a ZigZag-encoded signed integer as varint.
97
+
98
+ Args:
99
+ buffer: Destination buffer (must have at least 5 bytes from offset)
100
+ offset: Starting offset in buffer
101
+ value: Signed value to encode
102
+
103
+ Returns:
104
+ Number of bytes written (1-5)
105
+ """
106
+ return write_varint(buffer, offset, zigzag_encode(value))
107
+
108
+
109
+ def read_varint(data: bytes, offset: int) -> Tuple[int, int]:
110
+ """
111
+ Read a varint from bytes at the given offset.
112
+
113
+ Args:
114
+ data: Source buffer
115
+ offset: Starting offset in buffer
116
+
117
+ Returns:
118
+ Tuple of (decoded_value, bytes_read)
119
+
120
+ Raises:
121
+ EOFError: If buffer ends before varint completes
122
+ ValueError: If varint is malformed (too long)
123
+ """
124
+ result = 0
125
+ shift = 0
126
+ i = 0
127
+
128
+ while True:
129
+ if offset + i >= len(data):
130
+ raise EOFError("Unexpected end of varint")
131
+
132
+ b = data[offset + i]
133
+ result |= (b & 0x7F) << shift
134
+ i += 1
135
+
136
+ if (b & 0x80) == 0:
137
+ break
138
+
139
+ shift += 7
140
+ if shift >= 35:
141
+ raise ValueError("Varint too long (corrupted data)")
142
+
143
+ return result, i
144
+
145
+
146
+ def read_zigzag(data: bytes, offset: int) -> Tuple[int, int]:
147
+ """
148
+ Read a ZigZag-encoded signed integer.
149
+
150
+ Args:
151
+ data: Source buffer
152
+ offset: Starting offset in buffer
153
+
154
+ Returns:
155
+ Tuple of (decoded_signed_value, bytes_read)
156
+ """
157
+ encoded, bytes_read = read_varint(data, offset)
158
+ return zigzag_decode(encoded), bytes_read
159
+
160
+
161
+ def try_read_varint(data: bytes, offset: int) -> Tuple[bool, int, int]:
162
+ """
163
+ Try to read a varint, returning False if not enough data.
164
+
165
+ Args:
166
+ data: Source buffer
167
+ offset: Starting offset in buffer
168
+
169
+ Returns:
170
+ Tuple of (success, value, bytes_read)
171
+ If success is False, value and bytes_read are 0
172
+ """
173
+ result = 0
174
+ shift = 0
175
+
176
+ for i in range(min(5, len(data) - offset)):
177
+ b = data[offset + i]
178
+ result |= (b & 0x7F) << shift
179
+
180
+ if (b & 0x80) == 0:
181
+ return True, result, i + 1
182
+
183
+ shift += 7
184
+
185
+ return False, 0, 0
186
+
187
+
188
+ def encode_varint(value: int) -> bytes:
189
+ """
190
+ Convenience function to encode a value to varint bytes.
191
+
192
+ Args:
193
+ value: Unsigned value to encode
194
+
195
+ Returns:
196
+ Bytes containing the varint encoding
197
+ """
198
+ buffer = bytearray(MAX_BYTES_UINT32)
199
+ length = write_varint(buffer, 0, value)
200
+ return bytes(buffer[:length])
201
+
202
+
203
+ def encode_zigzag(value: int) -> bytes:
204
+ """
205
+ Convenience function to encode a signed value to ZigZag varint bytes.
206
+
207
+ Args:
208
+ value: Signed value to encode
209
+
210
+ Returns:
211
+ Bytes containing the ZigZag varint encoding
212
+ """
213
+ return encode_varint(zigzag_encode(value))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rocket-welder-sdk
3
- Version: 1.1.42
3
+ Version: 1.1.44
4
4
  Summary: High-performance video streaming SDK for RocketWelder services using ZeroBuffer IPC
5
5
  Home-page: https://github.com/modelingevolution/rocket-welder-sdk
6
6
  Author: ModelingEvolution
@@ -32,8 +32,6 @@ Requires-Dist: zerobuffer-ipc>=1.1.17
32
32
  Requires-Dist: pydantic>=2.5.0
33
33
  Requires-Dist: py-micro-plumberd>=0.1.8
34
34
  Requires-Dist: typing-extensions>=4.0.0
35
- Provides-Extra: nng
36
- Requires-Dist: pynng>=0.7.2; extra == "nng"
37
35
  Provides-Extra: dev
38
36
  Requires-Dist: pytest>=7.0; extra == "dev"
39
37
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
@@ -42,7 +40,6 @@ Requires-Dist: black>=22.0; extra == "dev"
42
40
  Requires-Dist: mypy>=1.0; extra == "dev"
43
41
  Requires-Dist: ruff>=0.1.0; extra == "dev"
44
42
  Requires-Dist: types-setuptools; extra == "dev"
45
- Requires-Dist: pynng>=0.7.2; extra == "dev"
46
43
  Dynamic: author
47
44
  Dynamic: home-page
48
45
  Dynamic: requires-python
@@ -1,40 +1,45 @@
1
- rocket_welder_sdk/__init__.py,sha256=xAy90cHORXwtphmNrvWaNs-YP4MAx3GIOZX_jJoIWxs,3530
1
+ rocket_welder_sdk/__init__.py,sha256=r2zUZzUMZuAPUy5Sat5EWaxeocIjRbdqBDoC1i0C89w,3413
2
+ rocket_welder_sdk/binary_frame_reader.py,sha256=5IyYO7yv07gLpQ9Toyw1zLkTrnqHLjxIeMVRn8B40-E,6270
3
+ rocket_welder_sdk/binary_frame_writer.py,sha256=2-VEJNHw0fL30TXsCBHyhHltvgx7KUahmbamwdSU8sw,6658
2
4
  rocket_welder_sdk/bytes_size.py,sha256=Myl29-wyWCIYdbMmgaxXebT8Dz8_Fwcr3fnfaNW81P0,7463
5
+ rocket_welder_sdk/confidence.py,sha256=AlgVM7gNDff3UxcQHhA5sJlTb4VeOMFAraF3W59AeSI,6498
3
6
  rocket_welder_sdk/connection_string.py,sha256=NIC6OiOXF-DeBFCWzgMFOWsenrSS45hY81j_HLMSpgo,9974
4
7
  rocket_welder_sdk/controllers.py,sha256=uuVyKsNHtpaIzQt8pXCAu7SywoFa7cfww8VSa4LNi_E,33130
8
+ rocket_welder_sdk/delta_frame.py,sha256=51RFWI2odKVak-d3qhv7S8DiXbk4M5f-bbuTT1unXfk,4518
5
9
  rocket_welder_sdk/frame_metadata.py,sha256=TMLIY47cIdIlxqk9xj7I3M8FZFmZ3GcVoLZht7prjQM,3929
6
10
  rocket_welder_sdk/gst_metadata.py,sha256=6Ov-DekGsKNuAo3UIc-g9fh4_SAPdHYQTnnPqCK3Ks0,16180
7
- rocket_welder_sdk/keypoints_protocol.py,sha256=NKiSPrevWG4_RrD6jtFxPjwftlaPWe1CqoFVKRMwp4k,21858
11
+ rocket_welder_sdk/keypoints_protocol.py,sha256=kN8ok6Ptpgl4txoQE9DpKhMoI7zr73vTw9CTvCICtgE,36921
8
12
  rocket_welder_sdk/opencv_controller.py,sha256=MDM6_yFBB9BaMa5jnZRqw7xZZB-WuLr7EPrrfHQ2DK4,9905
9
13
  rocket_welder_sdk/periodic_timer.py,sha256=hnObybmrnf3J47QrNKJhYAytLKwria016123NvPRfQ0,9369
10
14
  rocket_welder_sdk/py.typed,sha256=0cXFZXmes4Y-vnl4lO3HtyyyWaFNw85B7tJdFeCtHDc,67
11
- rocket_welder_sdk/rocket_welder_client.py,sha256=dhjjak7F6xTlLZDVl7TPPiX0KpyWb0DQhrTP3GrkuPM,26655
12
- rocket_welder_sdk/segmentation_result.py,sha256=c2C42C9l8iaRyTWR6ZOL1kLO4jHP_yjeVMf5DaKkY8E,17525
13
- rocket_welder_sdk/session_id.py,sha256=sRhzQw90shqq_DJVtrsSggcGZ775kz7cRfbI-1LMeSA,7027
15
+ rocket_welder_sdk/rocket_welder_client.py,sha256=VqxP-GkJKCZCjGZq2tVJ7v5yrh-ztXxlF3CT__818vg,23384
16
+ rocket_welder_sdk/segmentation_result.py,sha256=b3xpv6AZyWzm924cd1RfXwCuEwdkPL5TUA0gzT3HpBA,29153
17
+ rocket_welder_sdk/session_id.py,sha256=9GM4T6xmcNxR9gxZnDUlQGviNd3x3hT71q8y85XtH6g,1884
18
+ rocket_welder_sdk/varint.py,sha256=SmffemQCXToRzs3lb7hWQWDY7NmKv4XG_Wb0oeMrb_I,5058
14
19
  rocket_welder_sdk/external_controls/__init__.py,sha256=ldOLGhLLS5BQL8m4VKFYV0SvsNNlV2tghlc7rkqadU8,699
15
20
  rocket_welder_sdk/external_controls/contracts.py,sha256=3DU6pdpteN50gF2fsS7C2279dGjDa0tZLrLntkBa2LM,2607
16
21
  rocket_welder_sdk/external_controls/contracts_old.py,sha256=XWriuXJZu5caTSS0bcTIOZcKnj-IRCm96voA4gqLBfU,2980
17
- rocket_welder_sdk/high_level/__init__.py,sha256=jzLaAhmay_k03gaPgv9fdfBjn6b1xbiQoqSC-9Vk1aQ,1378
18
- rocket_welder_sdk/high_level/client.py,sha256=IFnbO9JI1KhnCljHiwFZbhx4qMW_z8BdTz4uAi0d7TA,8284
19
- rocket_welder_sdk/high_level/connection_strings.py,sha256=q1uZJQ7mt1RR-E8MJzIwG6vz3Ddruoc3pTCdTvg_pe4,10434
22
+ rocket_welder_sdk/high_level/__init__.py,sha256=OKbI3l0PFo1Cb_v0kbv16Wr05urz8heVGps0dJcTcG4,1507
23
+ rocket_welder_sdk/high_level/client.py,sha256=rVnnrn68PEvc68p5acuOBZfEjobWWed8XcssTRzxNr8,11502
24
+ rocket_welder_sdk/high_level/connection_strings.py,sha256=3EpFafyrmX_QNhs2nPscC63EMg5ikscvthu-0C6xC1c,9861
20
25
  rocket_welder_sdk/high_level/data_context.py,sha256=SXJvDpDBFi8Lm4XqSRSHK7YUUHuugXGo4ZRCb6_z5l0,4833
21
- rocket_welder_sdk/high_level/frame_sink_factory.py,sha256=JeQXp7_OoAQzcAb6XOsBt_Ewen_KbvYQ82YLDyx71tQ,4000
26
+ rocket_welder_sdk/high_level/frame_sink_factory.py,sha256=tLhMV_qnY-ZUtffIzhycM9znNjYguRumba4vphpNx7E,3493
22
27
  rocket_welder_sdk/high_level/schema.py,sha256=2Vv0rDwahtGswWB_ceaCdc7JDtmbkx4wE2jQePzeTpU,5367
23
- rocket_welder_sdk/high_level/transport_protocol.py,sha256=EFF0bgNn9hxRMj67FwU6MVu-UiEFINSGhd2VC8agrgc,7393
24
- rocket_welder_sdk/transport/__init__.py,sha256=iU5keW_VYHybvGBkDX1AxcCp9IhYvbqPBegu-w9IPX8,787
25
- rocket_welder_sdk/transport/frame_sink.py,sha256=YcsTcVGHSDWhhtt-PpWyjIZ9nImAHibgKoN_pcdr4VM,3334
26
- rocket_welder_sdk/transport/frame_source.py,sha256=G1rBAQS1AgOOdtASB0_CYon8g20hUGXpP2exCp5hlhk,2169
27
- rocket_welder_sdk/transport/nng_transport.py,sha256=o-qgcmHCGnwtdPe-mwwrC-a9H0rgS-VdH2QvU-6kFlI,5838
28
+ rocket_welder_sdk/high_level/transport_protocol.py,sha256=pdl7l1Tu9b7IYb95q6kzDY7-g1fbhSnhdV_gGhLeYzk,2943
29
+ rocket_welder_sdk/transport/__init__.py,sha256=J1iMvmIwC89PO8VZwd-qxKoAoB6BOxEY82aB9wLdB5c,950
30
+ rocket_welder_sdk/transport/frame_sink.py,sha256=--uL0kqxoz10Qya3eKpH6vjbMuSZHP0C9q6uwmbEo38,3332
31
+ rocket_welder_sdk/transport/frame_source.py,sha256=pgWFmqSLoIjGx8vW6Uv5uYCpcC49_iBSVU0oJZHDuPw,2171
28
32
  rocket_welder_sdk/transport/stream_transport.py,sha256=FhxFlZT-CTo6aPq6VclM1A_ecqfeHcwR4Ty1vmlt3W0,5886
29
33
  rocket_welder_sdk/transport/tcp_transport.py,sha256=Tui6nKgu50C1KV_UDANQKgVK3M52XN7zrkZTcWIpGmY,4745
30
34
  rocket_welder_sdk/transport/unix_socket_transport.py,sha256=t44Q2Fj6fMv53Ll561hN35-ZbHPdAQt0t9tUAXoL0rY,12245
35
+ rocket_welder_sdk/transport/websocket_transport.py,sha256=ti-Vr2I90JvgJgRfjqo2PfTYk5B86SYZhNofuxpwdBo,9591
31
36
  rocket_welder_sdk/ui/__init__.py,sha256=5-fCkv3vG0VVVZkselrifkMmx8d51EJp3nNT9mt4JK4,886
32
37
  rocket_welder_sdk/ui/controls.py,sha256=cRBxdUDWMOYlCxtw1PV11fTlte6qLxSfCGpr9bQiLcE,11162
33
38
  rocket_welder_sdk/ui/icons.py,sha256=DcDklZkPmiEzlOD4IR7VTJOtGPCuuh_OM_WN7ScghWE,8934592
34
39
  rocket_welder_sdk/ui/ui_events_projection.py,sha256=siiNhjLEBOPfTKw1ZhOPGkwIN5rLDH7V9VCZTNrhEtQ,7836
35
40
  rocket_welder_sdk/ui/ui_service.py,sha256=uRdpyJGoCQmtOli_HKSrxLwhZYG-XRuHIYdkmFz1zNk,12026
36
41
  rocket_welder_sdk/ui/value_types.py,sha256=f7OA_9zgXEDPoITc8v8SfAR23I4XeFhE3E2_GcAbR6k,1616
37
- rocket_welder_sdk-1.1.42.dist-info/METADATA,sha256=l7nDxLzpcdnP9GgA5iV_shN1hxpw4hcUsrNrjKxe1nA,24847
38
- rocket_welder_sdk-1.1.42.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
- rocket_welder_sdk-1.1.42.dist-info/top_level.txt,sha256=2iZvBjnwVCUW-uDE23-eJld5PZ9-mlPI69QiXM5IrTA,18
40
- rocket_welder_sdk-1.1.42.dist-info/RECORD,,
42
+ rocket_welder_sdk-1.1.44.dist-info/METADATA,sha256=XszsJzlnvEQCiPGYl-QxGK6TCTEYmWvi0AzvlHOqYKA,24739
43
+ rocket_welder_sdk-1.1.44.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
+ rocket_welder_sdk-1.1.44.dist-info/top_level.txt,sha256=2iZvBjnwVCUW-uDE23-eJld5PZ9-mlPI69QiXM5IrTA,18
45
+ rocket_welder_sdk-1.1.44.dist-info/RECORD,,
@@ -1,197 +0,0 @@
1
- """NNG transport using pynng library.
2
-
3
- NNG (nanomsg next generation) provides high-performance, scalable messaging patterns.
4
- Supported patterns:
5
- - Pub/Sub: One publisher to many subscribers
6
- - Push/Pull: Load-balanced distribution to workers
7
- """
8
-
9
- from typing import Any, Optional, cast
10
-
11
- import pynng
12
-
13
- from .frame_sink import IFrameSink
14
- from .frame_source import IFrameSource
15
-
16
-
17
- class NngFrameSink(IFrameSink):
18
- """
19
- Frame sink that publishes to NNG Pub/Sub or Push/Pull pattern.
20
-
21
- Each frame is sent as a single NNG message (no framing needed - NNG handles message boundaries).
22
- """
23
-
24
- def __init__(self, socket: Any, leave_open: bool = False):
25
- """
26
- Create an NNG frame sink from a socket.
27
-
28
- Args:
29
- socket: pynng socket (Publisher or Pusher)
30
- leave_open: If True, doesn't close socket on close
31
- """
32
- self._socket: Any = socket
33
- self._leave_open = leave_open
34
- self._closed = False
35
-
36
- @classmethod
37
- def create_publisher(cls, url: str) -> "NngFrameSink":
38
- """
39
- Create an NNG Publisher frame sink bound to the specified URL.
40
-
41
- Args:
42
- url: NNG URL (e.g., "tcp://127.0.0.1:5555", "ipc:///tmp/mysocket")
43
-
44
- Returns:
45
- Frame sink ready to publish messages
46
- """
47
- socket = pynng.Pub0()
48
- socket.listen(url)
49
- return cls(socket, leave_open=False)
50
-
51
- @classmethod
52
- def create_pusher(cls, url: str, bind_mode: bool = True) -> "NngFrameSink":
53
- """
54
- Create an NNG Pusher frame sink.
55
-
56
- Args:
57
- url: NNG URL (e.g., "tcp://127.0.0.1:5555", "ipc:///tmp/mysocket")
58
- bind_mode: If True, listens (bind); if False, dials (connect)
59
-
60
- Returns:
61
- Frame sink ready to push messages
62
- """
63
- socket = pynng.Push0()
64
- if bind_mode:
65
- socket.listen(url)
66
- else:
67
- socket.dial(url)
68
- return cls(socket, leave_open=False)
69
-
70
- def write_frame(self, frame_data: bytes) -> None:
71
- """Write frame to NNG socket (no length prefix - NNG handles message boundaries)."""
72
- if self._closed:
73
- raise ValueError("Cannot write to closed sink")
74
-
75
- self._socket.send(frame_data)
76
-
77
- async def write_frame_async(self, frame_data: bytes) -> None:
78
- """Write frame asynchronously."""
79
- if self._closed:
80
- raise ValueError("Cannot write to closed sink")
81
-
82
- await self._socket.asend(frame_data)
83
-
84
- def flush(self) -> None:
85
- """Flush is a no-op for NNG (data sent immediately)."""
86
- pass
87
-
88
- async def flush_async(self) -> None:
89
- """Flush asynchronously is a no-op for NNG."""
90
- pass
91
-
92
- def close(self) -> None:
93
- """Close the NNG sink."""
94
- if self._closed:
95
- return
96
- self._closed = True
97
- if not self._leave_open:
98
- self._socket.close()
99
-
100
- async def close_async(self) -> None:
101
- """Close the NNG sink asynchronously."""
102
- self.close()
103
-
104
-
105
- class NngFrameSource(IFrameSource):
106
- """
107
- Frame source that subscribes to NNG Pub/Sub or Pull pattern.
108
-
109
- Each NNG message is treated as a complete frame (no framing needed - NNG handles message boundaries).
110
- """
111
-
112
- def __init__(self, socket: Any, leave_open: bool = False):
113
- """
114
- Create an NNG frame source from a socket.
115
-
116
- Args:
117
- socket: pynng socket (Subscriber or Puller)
118
- leave_open: If True, doesn't close socket on close
119
- """
120
- self._socket: Any = socket
121
- self._leave_open = leave_open
122
- self._closed = False
123
-
124
- @classmethod
125
- def create_subscriber(cls, url: str, topic: bytes = b"") -> "NngFrameSource":
126
- """
127
- Create an NNG Subscriber frame source connected to the specified URL.
128
-
129
- Args:
130
- url: NNG URL (e.g., "tcp://127.0.0.1:5555", "ipc:///tmp/mysocket")
131
- topic: Optional topic filter (empty for all messages)
132
-
133
- Returns:
134
- Frame source ready to receive messages
135
- """
136
- socket = pynng.Sub0()
137
- socket.subscribe(topic)
138
- socket.dial(url)
139
- return cls(socket, leave_open=False)
140
-
141
- @classmethod
142
- def create_puller(cls, url: str, bind_mode: bool = True) -> "NngFrameSource":
143
- """
144
- Create an NNG Puller frame source.
145
-
146
- Args:
147
- url: NNG URL (e.g., "tcp://127.0.0.1:5555", "ipc:///tmp/mysocket")
148
- bind_mode: If True, listens (bind); if False, dials (connect)
149
-
150
- Returns:
151
- Frame source ready to pull messages
152
- """
153
- socket = pynng.Pull0()
154
- if bind_mode:
155
- socket.listen(url)
156
- else:
157
- socket.dial(url)
158
- return cls(socket, leave_open=False)
159
-
160
- @property
161
- def has_more_frames(self) -> bool:
162
- """Check if more frames available (NNG blocks waiting for messages)."""
163
- return not self._closed
164
-
165
- def read_frame(self) -> Optional[bytes]:
166
- """Read frame from NNG socket (blocking)."""
167
- if self._closed:
168
- return None
169
-
170
- try:
171
- return cast("bytes", self._socket.recv())
172
- except pynng.Closed:
173
- self._closed = True
174
- return None
175
-
176
- async def read_frame_async(self) -> Optional[bytes]:
177
- """Read frame asynchronously."""
178
- if self._closed:
179
- return None
180
-
181
- try:
182
- return cast("bytes", await self._socket.arecv())
183
- except pynng.Closed:
184
- self._closed = True
185
- return None
186
-
187
- def close(self) -> None:
188
- """Close the NNG source."""
189
- if self._closed:
190
- return
191
- self._closed = True
192
- if not self._leave_open:
193
- self._socket.close()
194
-
195
- async def close_async(self) -> None:
196
- """Close the NNG source asynchronously."""
197
- self.close()