socketwrapper 0.0.1__tar.gz
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.
- socketwrapper-0.0.1/LICENSE +21 -0
- socketwrapper-0.0.1/PKG-INFO +294 -0
- socketwrapper-0.0.1/README.md +243 -0
- socketwrapper-0.0.1/pyproject.toml +163 -0
- socketwrapper-0.0.1/setup.cfg +4 -0
- socketwrapper-0.0.1/socketwrapper/__init__.py +476 -0
- socketwrapper-0.0.1/socketwrapper/_utils.py +283 -0
- socketwrapper-0.0.1/socketwrapper/_varint.py +94 -0
- socketwrapper-0.0.1/socketwrapper/framing.py +143 -0
- socketwrapper-0.0.1/socketwrapper/protocols.py +39 -0
- socketwrapper-0.0.1/socketwrapper/py.typed +0 -0
- socketwrapper-0.0.1/socketwrapper.egg-info/PKG-INFO +294 -0
- socketwrapper-0.0.1/socketwrapper.egg-info/SOURCES.txt +15 -0
- socketwrapper-0.0.1/socketwrapper.egg-info/dependency_links.txt +1 -0
- socketwrapper-0.0.1/socketwrapper.egg-info/requires.txt +9 -0
- socketwrapper-0.0.1/socketwrapper.egg-info/top_level.txt +1 -0
- socketwrapper-0.0.1/socketwrapper.egg-info/zip-safe +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Felipe A Hernandez <ergoithz@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: socketwrapper
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: high level socket and pipe wrapper
|
|
5
|
+
Author: Felipe A Hernandez
|
|
6
|
+
Author-email: ergoithz@gmail.com
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 Felipe A Hernandez <ergoithz@gmail.com>
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: homepage, https://gitlab.com/ergoithz/socketwrapper
|
|
30
|
+
Project-URL: issue-tracker, https://gitlab.com/ergoithz/socketwrapper/-/issues
|
|
31
|
+
Project-URL: release-notes, https://gitlab.com/ergoithz/socketwrapper/-/releases
|
|
32
|
+
Project-URL: issue-new, https://gitlab.com/ergoithz/socketwrapper/-/issues/new
|
|
33
|
+
Project-URL: donations, https://ko-fi.com/s26me
|
|
34
|
+
Keywords: socket,pipe,ipc,asyncio
|
|
35
|
+
Classifier: Framework :: AsyncIO
|
|
36
|
+
Classifier: Intended Audience :: Developers
|
|
37
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
40
|
+
Classifier: Topic :: System :: Networking
|
|
41
|
+
Requires-Python: >=3.12
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Requires-Dist: typing-extensions>=4.15; python_version < "3.13"
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: coverage; extra == "dev"
|
|
47
|
+
Requires-Dist: ruff; extra == "dev"
|
|
48
|
+
Requires-Dist: wheel; extra == "dev"
|
|
49
|
+
Requires-Dist: yapf; extra == "dev"
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
# socketwrapper
|
|
53
|
+
|
|
54
|
+
This package provides high level wrappers for sockets and pipes:
|
|
55
|
+
- Thread-safe within both and between threads and asyncio realms.
|
|
56
|
+
- Managed sync recv and send operations with timeouts.
|
|
57
|
+
- Native asyncio recv_async and send_async operations.
|
|
58
|
+
- Pluggable message protocol (headered variable length data) supporting
|
|
59
|
+
header parsing, serialization and deserialization.
|
|
60
|
+
- Pluggable I/O protocol (file descriptor still required due asyncio).
|
|
61
|
+
|
|
62
|
+
## Motivation
|
|
63
|
+
|
|
64
|
+
There aren't a ton of high level asyncio socket wrappers out there providing
|
|
65
|
+
all we need for IPC: header/payload logic and support for both sockets and pipes.
|
|
66
|
+
|
|
67
|
+
Most implementations either don't have pluggable messaging protocols or it
|
|
68
|
+
doesn't support variable-size headers.
|
|
69
|
+
|
|
70
|
+
No other implementation is thread-safe across both asyncio and threading realms.
|
|
71
|
+
|
|
72
|
+
No other implementation directly supports wrapping multiprocessing Connections.
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
uv pip install socketwrapper
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Documentation
|
|
81
|
+
|
|
82
|
+
None other than this README, life's too short and I'm too busy with real life,
|
|
83
|
+
if you need better documentation consider donating to
|
|
84
|
+
[my ko-fi](https://ko-fi.com/s26me) stating that as the tip message,
|
|
85
|
+
check out how my docs look like at [mstache docs](https://mstache.readthedocs.io/en/latest/)
|
|
86
|
+
and [uactor docs](https://mstache.readthedocs.io/en/latest/).
|
|
87
|
+
|
|
88
|
+
### Puggable I/O: SocketLike protocol
|
|
89
|
+
|
|
90
|
+
The `socketwrapper.SocketLike` protocol, an small subset the socket interface,
|
|
91
|
+
is all what's required for any object to be wrap-able by `socketwrapper`.
|
|
92
|
+
|
|
93
|
+
```py
|
|
94
|
+
@typing.runtime_checkable
|
|
95
|
+
class SocketLike(typing.Protocol):
|
|
96
|
+
"""Protocol for socket-like objects accepted by socketwrapper socket classes."""
|
|
97
|
+
|
|
98
|
+
def fileno(self) -> int: ...
|
|
99
|
+
def send(self, data: collections.abc.Buffer, /) -> int: ...
|
|
100
|
+
def recv(self, bufsize: int, /) -> bytes: ...
|
|
101
|
+
def settimeout(self, timeout: float | None, /) -> None: ...
|
|
102
|
+
def close(self) -> None: ...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Special attention to:
|
|
106
|
+
- [fileno](https://docs.python.org/3.14/library/socket.html#socket.socket.fileno)
|
|
107
|
+
has to be a valid OS file descriptor.
|
|
108
|
+
- [settimeout](https://docs.python.org/3.14/library/socket.html#socket.socket.settimeout)
|
|
109
|
+
only requires support for `settimeout(.0)` (
|
|
110
|
+
[non-blocking semantics](https://docs.python.org/3.14/library/socket.html#notes-on-socket-timeouts)
|
|
111
|
+
), raising [ValueError](https://docs.python.org/3.14/library/exceptions.html#ValueError)
|
|
112
|
+
for any other value is fully supported in which case
|
|
113
|
+
[selectors.DefaultSelector](https://docs.python.org/3.14/library/selectors.html#selectors.DefaultSelector)
|
|
114
|
+
will be used for synchronous operations.
|
|
115
|
+
|
|
116
|
+
## Usage
|
|
117
|
+
|
|
118
|
+
### Simple IPC with pipe
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import os
|
|
122
|
+
import socketwrapper
|
|
123
|
+
|
|
124
|
+
with socketwrapper.pipe(framing=True) as (parent_reader, child_writer):
|
|
125
|
+
child_writer.inheritable = True
|
|
126
|
+
child_pid = os.fork() # replace with your own process fork/spawn logic
|
|
127
|
+
child_writer.inheritable = False # important, prevent socket leaks!
|
|
128
|
+
|
|
129
|
+
if child_pid:
|
|
130
|
+
print(f'Message {parent_reader.recv()!r} received')
|
|
131
|
+
else:
|
|
132
|
+
child_writer.send(b'Hello world!')
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Message b'Hello world!' received
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Simple asyncio IPC with pipe
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import asyncio
|
|
143
|
+
import os
|
|
144
|
+
import socketwrapper
|
|
145
|
+
|
|
146
|
+
async def parent(readable: socketwrapper.MessageReader) -> None:
|
|
147
|
+
print(f'Message {await readable.recv_async()!r} received')
|
|
148
|
+
|
|
149
|
+
async def child(writable: socketwrapper.MessageWriter) -> None:
|
|
150
|
+
await writable.send_async(b'Hello world!')
|
|
151
|
+
|
|
152
|
+
with socketwrapper.pipe(framing=True) as (parent_reader, child_writer):
|
|
153
|
+
child_writer.inheritable = True
|
|
154
|
+
child_pid = os.fork() # replace with your own process fork/spawn logic
|
|
155
|
+
child_writer.inheritable = False # important, prevent socket leaks!
|
|
156
|
+
|
|
157
|
+
asyncio.run(parent(parent_reader) if child_pid else child(child_writer))
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
Message b'Hello world!' received
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Simple bidirectional IPC with socketpair
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
import os
|
|
168
|
+
import socketwrapper
|
|
169
|
+
|
|
170
|
+
with socketwrapper.socketpair(framing=True) as (parent_duplex, child_duplex):
|
|
171
|
+
child_duplex.inheritable = True
|
|
172
|
+
child_pid = os.fork() # replace with your own process fork/spawn logic
|
|
173
|
+
child_duplex.inheritable = False # important, prevent socket leaks!
|
|
174
|
+
|
|
175
|
+
if child_pid:
|
|
176
|
+
parent_duplex.send(b'Hello child!')
|
|
177
|
+
print(f'Message {parent_duplex.recv()!r} received in parent')
|
|
178
|
+
|
|
179
|
+
else:
|
|
180
|
+
print(f'Message {child_duplex.recv()!r} received in child')
|
|
181
|
+
child_duplex.send(b'Hello parent!')
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
Message b'Hello child!' received in child
|
|
186
|
+
Message b'Hello parent!' received in parent
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Socketwrapper with multiprocessing.Pipe and asyncio
|
|
190
|
+
|
|
191
|
+
```py
|
|
192
|
+
import asyncio
|
|
193
|
+
import multiprocessing
|
|
194
|
+
import multiprocessing.connection
|
|
195
|
+
import socketwrapper
|
|
196
|
+
import socketwrapper.framing
|
|
197
|
+
|
|
198
|
+
def child(conn: multiprocessing.connection.Connection) -> None:
|
|
199
|
+
|
|
200
|
+
async def main() -> None:
|
|
201
|
+
with socketwrapper.MessageDuplex(conn, framing=socketwrapper.framing.MultiprocessingBytes) as child_duplex:
|
|
202
|
+
print(f'Message {await child_duplex.recv_async()!r} received in child')
|
|
203
|
+
await child_duplex.send_async(b'Hello parent!')
|
|
204
|
+
|
|
205
|
+
asyncio.run(main())
|
|
206
|
+
|
|
207
|
+
parent_conn, child_conn = multiprocessing.Pipe()
|
|
208
|
+
with parent_conn, child_conn:
|
|
209
|
+
child_process = multiprocessing.Process(target=child, args=(child_conn,))
|
|
210
|
+
child_process.start()
|
|
211
|
+
|
|
212
|
+
parent_conn.send_bytes(b'Hello child!')
|
|
213
|
+
print(f'Message {parent_conn.recv_bytes()!r} received in parent')
|
|
214
|
+
child_process.join(1)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Message b'Hello child!' received in child
|
|
219
|
+
Message b'Hello parent!' received in parent
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Custom socketwrapper framing with progress
|
|
223
|
+
|
|
224
|
+
```py
|
|
225
|
+
import collections.abc
|
|
226
|
+
import itertools
|
|
227
|
+
import os
|
|
228
|
+
import socketwrapper
|
|
229
|
+
import socketwrapper.framing
|
|
230
|
+
|
|
231
|
+
def progress(arrow: str, size: int, min_chunk: int = 1024) -> collections.abc.Generator[int, None, None]:
|
|
232
|
+
part_size = max(min_chunk, size // 100)
|
|
233
|
+
full_parts, last_size = divmod(size, part_size)
|
|
234
|
+
percent = 100 / (full_parts + 1 if last_size else full_parts)
|
|
235
|
+
|
|
236
|
+
for i in range(full_parts):
|
|
237
|
+
print(f'{arrow} {i * percent:6.2f}%')
|
|
238
|
+
yield part_size
|
|
239
|
+
|
|
240
|
+
if last_size:
|
|
241
|
+
print(f'{arrow} {full_parts * percent:6.2f}%')
|
|
242
|
+
yield last_size
|
|
243
|
+
|
|
244
|
+
print(f'{arrow} 100%')
|
|
245
|
+
|
|
246
|
+
class ProgressFraming(socketwrapper.framing.VarIntBytes):
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def frames(cls, buffer: bytearray) -> collections.abc.Generator[int, None, None]:
|
|
250
|
+
frames = super().frames(buffer)
|
|
251
|
+
yield from itertools.islice(frames, 2)
|
|
252
|
+
yield from progress('>', next(frames))
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def dumps(cls, data: bytes) -> collections.abc.Generator[memoryview, None, None]:
|
|
256
|
+
buffer = memoryview(b''.join(super().dumps(data)))
|
|
257
|
+
for size in progress('<', len(buffer)):
|
|
258
|
+
chunk, buffer = buffer[:size], buffer[size:]
|
|
259
|
+
yield chunk
|
|
260
|
+
|
|
261
|
+
with socketwrapper.socketpair(framing=ProgressFraming) as (parent_duplex, child_duplex):
|
|
262
|
+
child_duplex.inheritable = True
|
|
263
|
+
child_pid = os.fork() # replace with your own multiprocessing fork logic
|
|
264
|
+
child_duplex.inheritable = False # important, prevent socket leaks!
|
|
265
|
+
|
|
266
|
+
if child_pid:
|
|
267
|
+
payload = os.urandom(1024) * 1024
|
|
268
|
+
print(f'Sending {len(payload)} bytes!')
|
|
269
|
+
parent_duplex.send(payload)
|
|
270
|
+
else:
|
|
271
|
+
print(f'Received {len(child_duplex.recv())} bytes!')
|
|
272
|
+
```
|
|
273
|
+
```
|
|
274
|
+
Sending 1048576 bytes!
|
|
275
|
+
< 0.00%
|
|
276
|
+
< 0.99%
|
|
277
|
+
< 1.98%
|
|
278
|
+
< 2.97%
|
|
279
|
+
...
|
|
280
|
+
> 0.99%
|
|
281
|
+
< 13.86%
|
|
282
|
+
> 1.98%
|
|
283
|
+
< 14.85%
|
|
284
|
+
...
|
|
285
|
+
> 91.09%
|
|
286
|
+
< 99.01%
|
|
287
|
+
> 92.08%
|
|
288
|
+
< 100%
|
|
289
|
+
...
|
|
290
|
+
> 98.02%
|
|
291
|
+
> 99.01%
|
|
292
|
+
> 100%
|
|
293
|
+
Received 1048576 bytes!
|
|
294
|
+
```
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# socketwrapper
|
|
2
|
+
|
|
3
|
+
This package provides high level wrappers for sockets and pipes:
|
|
4
|
+
- Thread-safe within both and between threads and asyncio realms.
|
|
5
|
+
- Managed sync recv and send operations with timeouts.
|
|
6
|
+
- Native asyncio recv_async and send_async operations.
|
|
7
|
+
- Pluggable message protocol (headered variable length data) supporting
|
|
8
|
+
header parsing, serialization and deserialization.
|
|
9
|
+
- Pluggable I/O protocol (file descriptor still required due asyncio).
|
|
10
|
+
|
|
11
|
+
## Motivation
|
|
12
|
+
|
|
13
|
+
There aren't a ton of high level asyncio socket wrappers out there providing
|
|
14
|
+
all we need for IPC: header/payload logic and support for both sockets and pipes.
|
|
15
|
+
|
|
16
|
+
Most implementations either don't have pluggable messaging protocols or it
|
|
17
|
+
doesn't support variable-size headers.
|
|
18
|
+
|
|
19
|
+
No other implementation is thread-safe across both asyncio and threading realms.
|
|
20
|
+
|
|
21
|
+
No other implementation directly supports wrapping multiprocessing Connections.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
uv pip install socketwrapper
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
None other than this README, life's too short and I'm too busy with real life,
|
|
32
|
+
if you need better documentation consider donating to
|
|
33
|
+
[my ko-fi](https://ko-fi.com/s26me) stating that as the tip message,
|
|
34
|
+
check out how my docs look like at [mstache docs](https://mstache.readthedocs.io/en/latest/)
|
|
35
|
+
and [uactor docs](https://mstache.readthedocs.io/en/latest/).
|
|
36
|
+
|
|
37
|
+
### Puggable I/O: SocketLike protocol
|
|
38
|
+
|
|
39
|
+
The `socketwrapper.SocketLike` protocol, an small subset the socket interface,
|
|
40
|
+
is all what's required for any object to be wrap-able by `socketwrapper`.
|
|
41
|
+
|
|
42
|
+
```py
|
|
43
|
+
@typing.runtime_checkable
|
|
44
|
+
class SocketLike(typing.Protocol):
|
|
45
|
+
"""Protocol for socket-like objects accepted by socketwrapper socket classes."""
|
|
46
|
+
|
|
47
|
+
def fileno(self) -> int: ...
|
|
48
|
+
def send(self, data: collections.abc.Buffer, /) -> int: ...
|
|
49
|
+
def recv(self, bufsize: int, /) -> bytes: ...
|
|
50
|
+
def settimeout(self, timeout: float | None, /) -> None: ...
|
|
51
|
+
def close(self) -> None: ...
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Special attention to:
|
|
55
|
+
- [fileno](https://docs.python.org/3.14/library/socket.html#socket.socket.fileno)
|
|
56
|
+
has to be a valid OS file descriptor.
|
|
57
|
+
- [settimeout](https://docs.python.org/3.14/library/socket.html#socket.socket.settimeout)
|
|
58
|
+
only requires support for `settimeout(.0)` (
|
|
59
|
+
[non-blocking semantics](https://docs.python.org/3.14/library/socket.html#notes-on-socket-timeouts)
|
|
60
|
+
), raising [ValueError](https://docs.python.org/3.14/library/exceptions.html#ValueError)
|
|
61
|
+
for any other value is fully supported in which case
|
|
62
|
+
[selectors.DefaultSelector](https://docs.python.org/3.14/library/selectors.html#selectors.DefaultSelector)
|
|
63
|
+
will be used for synchronous operations.
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### Simple IPC with pipe
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import os
|
|
71
|
+
import socketwrapper
|
|
72
|
+
|
|
73
|
+
with socketwrapper.pipe(framing=True) as (parent_reader, child_writer):
|
|
74
|
+
child_writer.inheritable = True
|
|
75
|
+
child_pid = os.fork() # replace with your own process fork/spawn logic
|
|
76
|
+
child_writer.inheritable = False # important, prevent socket leaks!
|
|
77
|
+
|
|
78
|
+
if child_pid:
|
|
79
|
+
print(f'Message {parent_reader.recv()!r} received')
|
|
80
|
+
else:
|
|
81
|
+
child_writer.send(b'Hello world!')
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Message b'Hello world!' received
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Simple asyncio IPC with pipe
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import asyncio
|
|
92
|
+
import os
|
|
93
|
+
import socketwrapper
|
|
94
|
+
|
|
95
|
+
async def parent(readable: socketwrapper.MessageReader) -> None:
|
|
96
|
+
print(f'Message {await readable.recv_async()!r} received')
|
|
97
|
+
|
|
98
|
+
async def child(writable: socketwrapper.MessageWriter) -> None:
|
|
99
|
+
await writable.send_async(b'Hello world!')
|
|
100
|
+
|
|
101
|
+
with socketwrapper.pipe(framing=True) as (parent_reader, child_writer):
|
|
102
|
+
child_writer.inheritable = True
|
|
103
|
+
child_pid = os.fork() # replace with your own process fork/spawn logic
|
|
104
|
+
child_writer.inheritable = False # important, prevent socket leaks!
|
|
105
|
+
|
|
106
|
+
asyncio.run(parent(parent_reader) if child_pid else child(child_writer))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Message b'Hello world!' received
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Simple bidirectional IPC with socketpair
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
import os
|
|
117
|
+
import socketwrapper
|
|
118
|
+
|
|
119
|
+
with socketwrapper.socketpair(framing=True) as (parent_duplex, child_duplex):
|
|
120
|
+
child_duplex.inheritable = True
|
|
121
|
+
child_pid = os.fork() # replace with your own process fork/spawn logic
|
|
122
|
+
child_duplex.inheritable = False # important, prevent socket leaks!
|
|
123
|
+
|
|
124
|
+
if child_pid:
|
|
125
|
+
parent_duplex.send(b'Hello child!')
|
|
126
|
+
print(f'Message {parent_duplex.recv()!r} received in parent')
|
|
127
|
+
|
|
128
|
+
else:
|
|
129
|
+
print(f'Message {child_duplex.recv()!r} received in child')
|
|
130
|
+
child_duplex.send(b'Hello parent!')
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
Message b'Hello child!' received in child
|
|
135
|
+
Message b'Hello parent!' received in parent
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Socketwrapper with multiprocessing.Pipe and asyncio
|
|
139
|
+
|
|
140
|
+
```py
|
|
141
|
+
import asyncio
|
|
142
|
+
import multiprocessing
|
|
143
|
+
import multiprocessing.connection
|
|
144
|
+
import socketwrapper
|
|
145
|
+
import socketwrapper.framing
|
|
146
|
+
|
|
147
|
+
def child(conn: multiprocessing.connection.Connection) -> None:
|
|
148
|
+
|
|
149
|
+
async def main() -> None:
|
|
150
|
+
with socketwrapper.MessageDuplex(conn, framing=socketwrapper.framing.MultiprocessingBytes) as child_duplex:
|
|
151
|
+
print(f'Message {await child_duplex.recv_async()!r} received in child')
|
|
152
|
+
await child_duplex.send_async(b'Hello parent!')
|
|
153
|
+
|
|
154
|
+
asyncio.run(main())
|
|
155
|
+
|
|
156
|
+
parent_conn, child_conn = multiprocessing.Pipe()
|
|
157
|
+
with parent_conn, child_conn:
|
|
158
|
+
child_process = multiprocessing.Process(target=child, args=(child_conn,))
|
|
159
|
+
child_process.start()
|
|
160
|
+
|
|
161
|
+
parent_conn.send_bytes(b'Hello child!')
|
|
162
|
+
print(f'Message {parent_conn.recv_bytes()!r} received in parent')
|
|
163
|
+
child_process.join(1)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
Message b'Hello child!' received in child
|
|
168
|
+
Message b'Hello parent!' received in parent
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Custom socketwrapper framing with progress
|
|
172
|
+
|
|
173
|
+
```py
|
|
174
|
+
import collections.abc
|
|
175
|
+
import itertools
|
|
176
|
+
import os
|
|
177
|
+
import socketwrapper
|
|
178
|
+
import socketwrapper.framing
|
|
179
|
+
|
|
180
|
+
def progress(arrow: str, size: int, min_chunk: int = 1024) -> collections.abc.Generator[int, None, None]:
|
|
181
|
+
part_size = max(min_chunk, size // 100)
|
|
182
|
+
full_parts, last_size = divmod(size, part_size)
|
|
183
|
+
percent = 100 / (full_parts + 1 if last_size else full_parts)
|
|
184
|
+
|
|
185
|
+
for i in range(full_parts):
|
|
186
|
+
print(f'{arrow} {i * percent:6.2f}%')
|
|
187
|
+
yield part_size
|
|
188
|
+
|
|
189
|
+
if last_size:
|
|
190
|
+
print(f'{arrow} {full_parts * percent:6.2f}%')
|
|
191
|
+
yield last_size
|
|
192
|
+
|
|
193
|
+
print(f'{arrow} 100%')
|
|
194
|
+
|
|
195
|
+
class ProgressFraming(socketwrapper.framing.VarIntBytes):
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def frames(cls, buffer: bytearray) -> collections.abc.Generator[int, None, None]:
|
|
199
|
+
frames = super().frames(buffer)
|
|
200
|
+
yield from itertools.islice(frames, 2)
|
|
201
|
+
yield from progress('>', next(frames))
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def dumps(cls, data: bytes) -> collections.abc.Generator[memoryview, None, None]:
|
|
205
|
+
buffer = memoryview(b''.join(super().dumps(data)))
|
|
206
|
+
for size in progress('<', len(buffer)):
|
|
207
|
+
chunk, buffer = buffer[:size], buffer[size:]
|
|
208
|
+
yield chunk
|
|
209
|
+
|
|
210
|
+
with socketwrapper.socketpair(framing=ProgressFraming) as (parent_duplex, child_duplex):
|
|
211
|
+
child_duplex.inheritable = True
|
|
212
|
+
child_pid = os.fork() # replace with your own multiprocessing fork logic
|
|
213
|
+
child_duplex.inheritable = False # important, prevent socket leaks!
|
|
214
|
+
|
|
215
|
+
if child_pid:
|
|
216
|
+
payload = os.urandom(1024) * 1024
|
|
217
|
+
print(f'Sending {len(payload)} bytes!')
|
|
218
|
+
parent_duplex.send(payload)
|
|
219
|
+
else:
|
|
220
|
+
print(f'Received {len(child_duplex.recv())} bytes!')
|
|
221
|
+
```
|
|
222
|
+
```
|
|
223
|
+
Sending 1048576 bytes!
|
|
224
|
+
< 0.00%
|
|
225
|
+
< 0.99%
|
|
226
|
+
< 1.98%
|
|
227
|
+
< 2.97%
|
|
228
|
+
...
|
|
229
|
+
> 0.99%
|
|
230
|
+
< 13.86%
|
|
231
|
+
> 1.98%
|
|
232
|
+
< 14.85%
|
|
233
|
+
...
|
|
234
|
+
> 91.09%
|
|
235
|
+
< 99.01%
|
|
236
|
+
> 92.08%
|
|
237
|
+
< 100%
|
|
238
|
+
...
|
|
239
|
+
> 98.02%
|
|
240
|
+
> 99.01%
|
|
241
|
+
> 100%
|
|
242
|
+
Received 1048576 bytes!
|
|
243
|
+
```
|