socketwrapper 0.0.1__tar.gz → 0.0.3__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/socketwrapper.egg-info → socketwrapper-0.0.3}/PKG-INFO +107 -44
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/README.md +100 -41
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/pyproject.toml +6 -3
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/socketwrapper/__init__.py +60 -50
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/socketwrapper/_utils.py +124 -62
- socketwrapper-0.0.3/socketwrapper/framing.py +254 -0
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/socketwrapper/protocols.py +10 -2
- {socketwrapper-0.0.1 → socketwrapper-0.0.3/socketwrapper.egg-info}/PKG-INFO +107 -44
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/socketwrapper.egg-info/requires.txt +5 -1
- socketwrapper-0.0.1/socketwrapper/framing.py +0 -143
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/LICENSE +0 -0
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/setup.cfg +0 -0
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/socketwrapper/_varint.py +0 -0
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/socketwrapper/py.typed +0 -0
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/socketwrapper.egg-info/SOURCES.txt +0 -0
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/socketwrapper.egg-info/dependency_links.txt +0 -0
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/socketwrapper.egg-info/top_level.txt +0 -0
- {socketwrapper-0.0.1 → socketwrapper-0.0.3}/socketwrapper.egg-info/zip-safe +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: socketwrapper
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary: high level socket and pipe
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: high level socket and pipe wrappers
|
|
5
5
|
Author: Felipe A Hernandez
|
|
6
6
|
Author-email: ergoithz@gmail.com
|
|
7
7
|
License: MIT License
|
|
@@ -27,6 +27,7 @@ License: MIT License
|
|
|
27
27
|
SOFTWARE.
|
|
28
28
|
|
|
29
29
|
Project-URL: homepage, https://gitlab.com/ergoithz/socketwrapper
|
|
30
|
+
Project-URL: repository, https://gitlab.com/ergoithz/socketwrapper
|
|
30
31
|
Project-URL: issue-tracker, https://gitlab.com/ergoithz/socketwrapper/-/issues
|
|
31
32
|
Project-URL: release-notes, https://gitlab.com/ergoithz/socketwrapper/-/releases
|
|
32
33
|
Project-URL: issue-new, https://gitlab.com/ergoithz/socketwrapper/-/issues/new
|
|
@@ -41,9 +42,12 @@ Classifier: Topic :: System :: Networking
|
|
|
41
42
|
Requires-Python: >=3.12
|
|
42
43
|
Description-Content-Type: text/markdown
|
|
43
44
|
License-File: LICENSE
|
|
44
|
-
Requires-Dist: typing-extensions>=4.
|
|
45
|
+
Requires-Dist: typing-extensions>=4.6; python_version < "3.13"
|
|
46
|
+
Provides-Extra: msgpack
|
|
47
|
+
Requires-Dist: msgpack; extra == "msgpack"
|
|
45
48
|
Provides-Extra: dev
|
|
46
49
|
Requires-Dist: coverage; extra == "dev"
|
|
50
|
+
Requires-Dist: msgpack; extra == "dev"
|
|
47
51
|
Requires-Dist: ruff; extra == "dev"
|
|
48
52
|
Requires-Dist: wheel; extra == "dev"
|
|
49
53
|
Requires-Dist: yapf; extra == "dev"
|
|
@@ -52,24 +56,26 @@ Dynamic: license-file
|
|
|
52
56
|
# socketwrapper
|
|
53
57
|
|
|
54
58
|
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
59
|
|
|
62
|
-
|
|
60
|
+
- Thread-safe within both threads and asyncio realms, and between them.
|
|
61
|
+
- Managed sync `recv` and `send` operations with timeouts.
|
|
62
|
+
- Native asyncio `recv_async` and `send_async` operations.
|
|
63
|
+
- Pluggable message protocol (headered variable length data) supporting variable-length header parsing, payload serialization and deserialization.
|
|
64
|
+
- Pluggable I/O protocol (OS file descriptor still required).
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
all we need for IPC: header/payload logic and support for both sockets and pipes.
|
|
66
|
+
Builtin message protocols (framing):
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
- `socketwrapper.framing.VarIntBytes`: varint-headered bytes (default with `framing=True`).
|
|
69
|
+
- `socketwrapper.framing.MultiprocessingBytes` (if platform is supported): [multiprocessing.connection.Connection](https://docs.python.org/3.14/library/multiprocessing.html#connection-objects) bytes.
|
|
70
|
+
- `socketwrapper.framing.Multiprocessing` (if platform is supported): [multiprocessing.connection.Connection](https://docs.python.org/3.14/library/multiprocessing.html#connection-objects) pickled data.
|
|
71
|
+
- `socketwrapper.framing.MsgPack` (if `msgpack` is available): unheadered [msgpack](https://pypi.org/project/msgpack/) data stream.
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
## Motivation
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
- There aren't a ton of high level asyncio socket wrappers out there providing all we need for IPC: header/payload message logic and support for both sockets and pipes.
|
|
76
|
+
- Most implementations got either hardcoded messaging protocols or require a fixed-size header.
|
|
77
|
+
- No implementation was thread-safe between both asyncio and threading realms.
|
|
78
|
+
- No implementation directly supported wrapping multiprocessing Connections into an asyncio-native interface.
|
|
73
79
|
|
|
74
80
|
## Installation
|
|
75
81
|
|
|
@@ -77,18 +83,35 @@ No other implementation directly supports wrapping multiprocessing Connections.
|
|
|
77
83
|
uv pip install socketwrapper
|
|
78
84
|
```
|
|
79
85
|
|
|
86
|
+
Or with optional [msgpack](https://pypi.org/project/msgpack/) support.
|
|
87
|
+
```sh
|
|
88
|
+
uv pip install 'socketwrapper[msgpack]'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Changelog
|
|
92
|
+
|
|
93
|
+
### 0.0.3 - 2025.11.08
|
|
94
|
+
|
|
95
|
+
- New: optional support for `recv_into` in socket-like objects.
|
|
96
|
+
- Optimization: recv operations will now use `sock.recv_into` (if available) to reduce memory allocation overhead.
|
|
97
|
+
|
|
98
|
+
### 0.0.2 - 2025.11.05
|
|
99
|
+
|
|
100
|
+
- Breaking: `MessageFraming.frames` and `MessageFraming.loads` now receive `io.BytesIO` instead of `bytearray`.
|
|
101
|
+
- New: optional msgpack support (`msgpack` extra).
|
|
102
|
+
- Fix: busy reads no longer blocking asyncio event loop.
|
|
103
|
+
- Typing: expose `SizedBuffer` and deprecate `SendPayload` (removal expected in `0.1.0`).
|
|
104
|
+
- Typing: `SocketWriter.send` and `SocketWriter.send_async` data is now `SizedBuffer`.
|
|
105
|
+
|
|
80
106
|
## Documentation
|
|
81
107
|
|
|
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/).
|
|
108
|
+
None other than this README, life's too short and I'm too busy with real life, if you need better documentation consider donating to [my ko-fi](https://ko-fi.com/s26me) stating that as a tip message, check out how my docs look like at [mstache docs](https://mstache.readthedocs.io/en/latest/) and [uactor docs](https://mstache.readthedocs.io/en/latest/).
|
|
87
109
|
|
|
88
110
|
### Puggable I/O: SocketLike protocol
|
|
89
111
|
|
|
90
|
-
The `
|
|
91
|
-
|
|
112
|
+
The `protocols.SocketLike` protocol, a small subset the socket interface, is all what's required for any object to be wrap-able by `socketwrapper`.
|
|
113
|
+
|
|
114
|
+
Additional methods defined in `protocols.OptionalSocketLike` can be optionally implemented enabling optimized code paths.
|
|
92
115
|
|
|
93
116
|
```py
|
|
94
117
|
@typing.runtime_checkable
|
|
@@ -100,18 +123,19 @@ class SocketLike(typing.Protocol):
|
|
|
100
123
|
def recv(self, bufsize: int, /) -> bytes: ...
|
|
101
124
|
def settimeout(self, timeout: float | None, /) -> None: ...
|
|
102
125
|
def close(self) -> None: ...
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@typing.runtime_checkable
|
|
129
|
+
class OptionalSocketLike(SocketLike, typing.Protocol):
|
|
130
|
+
"""Protocol for optional socket-like object methods accepted by socketwrapper socket classes."""
|
|
131
|
+
|
|
132
|
+
def recv_into(self, buffer: collections.abc.Buffer, /) -> bytes: ...
|
|
133
|
+
|
|
103
134
|
```
|
|
104
135
|
|
|
105
136
|
Special attention to:
|
|
106
|
-
- [fileno](https://docs.python.org/3.14/library/socket.html#socket.socket.fileno)
|
|
107
|
-
|
|
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.
|
|
137
|
+
- [fileno](https://docs.python.org/3.14/library/socket.html#socket.socket.fileno) has to be a valid OS file descriptor.
|
|
138
|
+
- [settimeout](https://docs.python.org/3.14/library/socket.html#socket.socket.settimeout) must support `settimeout(.0)` ([non-blocking semantics](https://docs.python.org/3.14/library/socket.html#notes-on-socket-timeouts)), raising [ValueError](https://docs.python.org/3.14/library/exceptions.html#ValueError) for any other value will be handled, relying on [selectors.DefaultSelector](https://docs.python.org/3.14/library/selectors.html#selectors.DefaultSelector) for synchronous operations.
|
|
115
139
|
|
|
116
140
|
## Usage
|
|
117
141
|
|
|
@@ -131,11 +155,31 @@ with socketwrapper.pipe(framing=True) as (parent_reader, child_writer):
|
|
|
131
155
|
else:
|
|
132
156
|
child_writer.send(b'Hello world!')
|
|
133
157
|
```
|
|
134
|
-
|
|
135
158
|
```
|
|
136
159
|
Message b'Hello world!' received
|
|
137
160
|
```
|
|
138
161
|
|
|
162
|
+
### Simple IPC with pipe using msgpack
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
import os
|
|
166
|
+
import socketwrapper
|
|
167
|
+
import socketwrapper.framing as framing
|
|
168
|
+
|
|
169
|
+
with socketwrapper.pipe(framing=framing.MsgPack()) as (parent_reader, child_writer):
|
|
170
|
+
child_writer.inheritable = True
|
|
171
|
+
child_pid = os.fork() # replace with your own process fork/spawn logic
|
|
172
|
+
child_writer.inheritable = False # important, prevent socket leaks!
|
|
173
|
+
|
|
174
|
+
if child_pid:
|
|
175
|
+
print(f'Message {parent_reader.recv()!r} received')
|
|
176
|
+
else:
|
|
177
|
+
child_writer.send({'data': b'Hello world!'})
|
|
178
|
+
```
|
|
179
|
+
```
|
|
180
|
+
Message {'data': b'Hello world!'} received
|
|
181
|
+
```
|
|
182
|
+
|
|
139
183
|
### Simple asyncio IPC with pipe
|
|
140
184
|
|
|
141
185
|
```python
|
|
@@ -156,7 +200,6 @@ with socketwrapper.pipe(framing=True) as (parent_reader, child_writer):
|
|
|
156
200
|
|
|
157
201
|
asyncio.run(parent(parent_reader) if child_pid else child(child_writer))
|
|
158
202
|
```
|
|
159
|
-
|
|
160
203
|
```
|
|
161
204
|
Message b'Hello world!' received
|
|
162
205
|
```
|
|
@@ -180,7 +223,6 @@ with socketwrapper.socketpair(framing=True) as (parent_duplex, child_duplex):
|
|
|
180
223
|
print(f'Message {child_duplex.recv()!r} received in child')
|
|
181
224
|
child_duplex.send(b'Hello parent!')
|
|
182
225
|
```
|
|
183
|
-
|
|
184
226
|
```
|
|
185
227
|
Message b'Hello child!' received in child
|
|
186
228
|
Message b'Hello parent!' received in parent
|
|
@@ -204,25 +246,46 @@ def child(conn: multiprocessing.connection.Connection) -> None:
|
|
|
204
246
|
|
|
205
247
|
asyncio.run(main())
|
|
206
248
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
249
|
+
if __name__ == '__main__':
|
|
250
|
+
parent_conn, child_conn = multiprocessing.Pipe()
|
|
251
|
+
with parent_conn, child_conn:
|
|
252
|
+
child_process = multiprocessing.Process(target=child, args=(child_conn,))
|
|
253
|
+
child_process.start()
|
|
211
254
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
255
|
+
parent_conn.send_bytes(b'Hello child!')
|
|
256
|
+
print(f'Message {parent_conn.recv_bytes()!r} received in parent')
|
|
257
|
+
child_process.join(1)
|
|
215
258
|
```
|
|
216
|
-
|
|
217
259
|
```
|
|
218
260
|
Message b'Hello child!' received in child
|
|
219
261
|
Message b'Hello parent!' received in parent
|
|
220
262
|
```
|
|
221
263
|
|
|
264
|
+
### Socketwrapper for cross-interpreter communication
|
|
265
|
+
|
|
266
|
+
```py
|
|
267
|
+
import concurrent.futures
|
|
268
|
+
import socketwrapper
|
|
269
|
+
|
|
270
|
+
def child(child_fileno: int) -> None:
|
|
271
|
+
child_writer = socketwrapper.MessageWriter(child_fileno)
|
|
272
|
+
child_writer.send(b'Hello World')
|
|
273
|
+
|
|
274
|
+
if __name__ == '__main__':
|
|
275
|
+
with (socketwrapper.pipe(framing=True) as (parent_reader, child_writer),
|
|
276
|
+
concurrent.futures.InterpreterPoolExecutor() as pool):
|
|
277
|
+
pool.submit(child, child_writer.fileno())
|
|
278
|
+
print(f'Message {parent_reader.recv()!r} received')
|
|
279
|
+
```
|
|
280
|
+
```
|
|
281
|
+
Message b'Hello World' received
|
|
282
|
+
```
|
|
283
|
+
|
|
222
284
|
### Custom socketwrapper framing with progress
|
|
223
285
|
|
|
224
286
|
```py
|
|
225
287
|
import collections.abc
|
|
288
|
+
import io
|
|
226
289
|
import itertools
|
|
227
290
|
import os
|
|
228
291
|
import socketwrapper
|
|
@@ -246,7 +309,7 @@ def progress(arrow: str, size: int, min_chunk: int = 1024) -> collections.abc.Ge
|
|
|
246
309
|
class ProgressFraming(socketwrapper.framing.VarIntBytes):
|
|
247
310
|
|
|
248
311
|
@classmethod
|
|
249
|
-
def frames(cls, buffer:
|
|
312
|
+
def frames(cls, buffer: io.BytesIO) -> collections.abc.Generator[int, None, None]:
|
|
250
313
|
frames = super().frames(buffer)
|
|
251
314
|
yield from itertools.islice(frames, 2)
|
|
252
315
|
yield from progress('>', next(frames))
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
# socketwrapper
|
|
2
2
|
|
|
3
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
4
|
|
|
11
|
-
|
|
5
|
+
- Thread-safe within both threads and asyncio realms, and between them.
|
|
6
|
+
- Managed sync `recv` and `send` operations with timeouts.
|
|
7
|
+
- Native asyncio `recv_async` and `send_async` operations.
|
|
8
|
+
- Pluggable message protocol (headered variable length data) supporting variable-length header parsing, payload serialization and deserialization.
|
|
9
|
+
- Pluggable I/O protocol (OS file descriptor still required).
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
all we need for IPC: header/payload logic and support for both sockets and pipes.
|
|
11
|
+
Builtin message protocols (framing):
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
- `socketwrapper.framing.VarIntBytes`: varint-headered bytes (default with `framing=True`).
|
|
14
|
+
- `socketwrapper.framing.MultiprocessingBytes` (if platform is supported): [multiprocessing.connection.Connection](https://docs.python.org/3.14/library/multiprocessing.html#connection-objects) bytes.
|
|
15
|
+
- `socketwrapper.framing.Multiprocessing` (if platform is supported): [multiprocessing.connection.Connection](https://docs.python.org/3.14/library/multiprocessing.html#connection-objects) pickled data.
|
|
16
|
+
- `socketwrapper.framing.MsgPack` (if `msgpack` is available): unheadered [msgpack](https://pypi.org/project/msgpack/) data stream.
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
## Motivation
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
- There aren't a ton of high level asyncio socket wrappers out there providing all we need for IPC: header/payload message logic and support for both sockets and pipes.
|
|
21
|
+
- Most implementations got either hardcoded messaging protocols or require a fixed-size header.
|
|
22
|
+
- No implementation was thread-safe between both asyncio and threading realms.
|
|
23
|
+
- No implementation directly supported wrapping multiprocessing Connections into an asyncio-native interface.
|
|
22
24
|
|
|
23
25
|
## Installation
|
|
24
26
|
|
|
@@ -26,18 +28,35 @@ No other implementation directly supports wrapping multiprocessing Connections.
|
|
|
26
28
|
uv pip install socketwrapper
|
|
27
29
|
```
|
|
28
30
|
|
|
31
|
+
Or with optional [msgpack](https://pypi.org/project/msgpack/) support.
|
|
32
|
+
```sh
|
|
33
|
+
uv pip install 'socketwrapper[msgpack]'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Changelog
|
|
37
|
+
|
|
38
|
+
### 0.0.3 - 2025.11.08
|
|
39
|
+
|
|
40
|
+
- New: optional support for `recv_into` in socket-like objects.
|
|
41
|
+
- Optimization: recv operations will now use `sock.recv_into` (if available) to reduce memory allocation overhead.
|
|
42
|
+
|
|
43
|
+
### 0.0.2 - 2025.11.05
|
|
44
|
+
|
|
45
|
+
- Breaking: `MessageFraming.frames` and `MessageFraming.loads` now receive `io.BytesIO` instead of `bytearray`.
|
|
46
|
+
- New: optional msgpack support (`msgpack` extra).
|
|
47
|
+
- Fix: busy reads no longer blocking asyncio event loop.
|
|
48
|
+
- Typing: expose `SizedBuffer` and deprecate `SendPayload` (removal expected in `0.1.0`).
|
|
49
|
+
- Typing: `SocketWriter.send` and `SocketWriter.send_async` data is now `SizedBuffer`.
|
|
50
|
+
|
|
29
51
|
## Documentation
|
|
30
52
|
|
|
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/).
|
|
53
|
+
None other than this README, life's too short and I'm too busy with real life, if you need better documentation consider donating to [my ko-fi](https://ko-fi.com/s26me) stating that as a tip message, check out how my docs look like at [mstache docs](https://mstache.readthedocs.io/en/latest/) and [uactor docs](https://mstache.readthedocs.io/en/latest/).
|
|
36
54
|
|
|
37
55
|
### Puggable I/O: SocketLike protocol
|
|
38
56
|
|
|
39
|
-
The `
|
|
40
|
-
|
|
57
|
+
The `protocols.SocketLike` protocol, a small subset the socket interface, is all what's required for any object to be wrap-able by `socketwrapper`.
|
|
58
|
+
|
|
59
|
+
Additional methods defined in `protocols.OptionalSocketLike` can be optionally implemented enabling optimized code paths.
|
|
41
60
|
|
|
42
61
|
```py
|
|
43
62
|
@typing.runtime_checkable
|
|
@@ -49,18 +68,19 @@ class SocketLike(typing.Protocol):
|
|
|
49
68
|
def recv(self, bufsize: int, /) -> bytes: ...
|
|
50
69
|
def settimeout(self, timeout: float | None, /) -> None: ...
|
|
51
70
|
def close(self) -> None: ...
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@typing.runtime_checkable
|
|
74
|
+
class OptionalSocketLike(SocketLike, typing.Protocol):
|
|
75
|
+
"""Protocol for optional socket-like object methods accepted by socketwrapper socket classes."""
|
|
76
|
+
|
|
77
|
+
def recv_into(self, buffer: collections.abc.Buffer, /) -> bytes: ...
|
|
78
|
+
|
|
52
79
|
```
|
|
53
80
|
|
|
54
81
|
Special attention to:
|
|
55
|
-
- [fileno](https://docs.python.org/3.14/library/socket.html#socket.socket.fileno)
|
|
56
|
-
|
|
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.
|
|
82
|
+
- [fileno](https://docs.python.org/3.14/library/socket.html#socket.socket.fileno) has to be a valid OS file descriptor.
|
|
83
|
+
- [settimeout](https://docs.python.org/3.14/library/socket.html#socket.socket.settimeout) must support `settimeout(.0)` ([non-blocking semantics](https://docs.python.org/3.14/library/socket.html#notes-on-socket-timeouts)), raising [ValueError](https://docs.python.org/3.14/library/exceptions.html#ValueError) for any other value will be handled, relying on [selectors.DefaultSelector](https://docs.python.org/3.14/library/selectors.html#selectors.DefaultSelector) for synchronous operations.
|
|
64
84
|
|
|
65
85
|
## Usage
|
|
66
86
|
|
|
@@ -80,11 +100,31 @@ with socketwrapper.pipe(framing=True) as (parent_reader, child_writer):
|
|
|
80
100
|
else:
|
|
81
101
|
child_writer.send(b'Hello world!')
|
|
82
102
|
```
|
|
83
|
-
|
|
84
103
|
```
|
|
85
104
|
Message b'Hello world!' received
|
|
86
105
|
```
|
|
87
106
|
|
|
107
|
+
### Simple IPC with pipe using msgpack
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import os
|
|
111
|
+
import socketwrapper
|
|
112
|
+
import socketwrapper.framing as framing
|
|
113
|
+
|
|
114
|
+
with socketwrapper.pipe(framing=framing.MsgPack()) as (parent_reader, child_writer):
|
|
115
|
+
child_writer.inheritable = True
|
|
116
|
+
child_pid = os.fork() # replace with your own process fork/spawn logic
|
|
117
|
+
child_writer.inheritable = False # important, prevent socket leaks!
|
|
118
|
+
|
|
119
|
+
if child_pid:
|
|
120
|
+
print(f'Message {parent_reader.recv()!r} received')
|
|
121
|
+
else:
|
|
122
|
+
child_writer.send({'data': b'Hello world!'})
|
|
123
|
+
```
|
|
124
|
+
```
|
|
125
|
+
Message {'data': b'Hello world!'} received
|
|
126
|
+
```
|
|
127
|
+
|
|
88
128
|
### Simple asyncio IPC with pipe
|
|
89
129
|
|
|
90
130
|
```python
|
|
@@ -105,7 +145,6 @@ with socketwrapper.pipe(framing=True) as (parent_reader, child_writer):
|
|
|
105
145
|
|
|
106
146
|
asyncio.run(parent(parent_reader) if child_pid else child(child_writer))
|
|
107
147
|
```
|
|
108
|
-
|
|
109
148
|
```
|
|
110
149
|
Message b'Hello world!' received
|
|
111
150
|
```
|
|
@@ -129,7 +168,6 @@ with socketwrapper.socketpair(framing=True) as (parent_duplex, child_duplex):
|
|
|
129
168
|
print(f'Message {child_duplex.recv()!r} received in child')
|
|
130
169
|
child_duplex.send(b'Hello parent!')
|
|
131
170
|
```
|
|
132
|
-
|
|
133
171
|
```
|
|
134
172
|
Message b'Hello child!' received in child
|
|
135
173
|
Message b'Hello parent!' received in parent
|
|
@@ -153,25 +191,46 @@ def child(conn: multiprocessing.connection.Connection) -> None:
|
|
|
153
191
|
|
|
154
192
|
asyncio.run(main())
|
|
155
193
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
194
|
+
if __name__ == '__main__':
|
|
195
|
+
parent_conn, child_conn = multiprocessing.Pipe()
|
|
196
|
+
with parent_conn, child_conn:
|
|
197
|
+
child_process = multiprocessing.Process(target=child, args=(child_conn,))
|
|
198
|
+
child_process.start()
|
|
160
199
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
200
|
+
parent_conn.send_bytes(b'Hello child!')
|
|
201
|
+
print(f'Message {parent_conn.recv_bytes()!r} received in parent')
|
|
202
|
+
child_process.join(1)
|
|
164
203
|
```
|
|
165
|
-
|
|
166
204
|
```
|
|
167
205
|
Message b'Hello child!' received in child
|
|
168
206
|
Message b'Hello parent!' received in parent
|
|
169
207
|
```
|
|
170
208
|
|
|
209
|
+
### Socketwrapper for cross-interpreter communication
|
|
210
|
+
|
|
211
|
+
```py
|
|
212
|
+
import concurrent.futures
|
|
213
|
+
import socketwrapper
|
|
214
|
+
|
|
215
|
+
def child(child_fileno: int) -> None:
|
|
216
|
+
child_writer = socketwrapper.MessageWriter(child_fileno)
|
|
217
|
+
child_writer.send(b'Hello World')
|
|
218
|
+
|
|
219
|
+
if __name__ == '__main__':
|
|
220
|
+
with (socketwrapper.pipe(framing=True) as (parent_reader, child_writer),
|
|
221
|
+
concurrent.futures.InterpreterPoolExecutor() as pool):
|
|
222
|
+
pool.submit(child, child_writer.fileno())
|
|
223
|
+
print(f'Message {parent_reader.recv()!r} received')
|
|
224
|
+
```
|
|
225
|
+
```
|
|
226
|
+
Message b'Hello World' received
|
|
227
|
+
```
|
|
228
|
+
|
|
171
229
|
### Custom socketwrapper framing with progress
|
|
172
230
|
|
|
173
231
|
```py
|
|
174
232
|
import collections.abc
|
|
233
|
+
import io
|
|
175
234
|
import itertools
|
|
176
235
|
import os
|
|
177
236
|
import socketwrapper
|
|
@@ -195,7 +254,7 @@ def progress(arrow: str, size: int, min_chunk: int = 1024) -> collections.abc.Ge
|
|
|
195
254
|
class ProgressFraming(socketwrapper.framing.VarIntBytes):
|
|
196
255
|
|
|
197
256
|
@classmethod
|
|
198
|
-
def frames(cls, buffer:
|
|
257
|
+
def frames(cls, buffer: io.BytesIO) -> collections.abc.Generator[int, None, None]:
|
|
199
258
|
frames = super().frames(buffer)
|
|
200
259
|
yield from itertools.islice(frames, 2)
|
|
201
260
|
yield from progress('>', next(frames))
|
|
@@ -4,8 +4,8 @@ build-backend = 'setuptools.build_meta'
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = 'socketwrapper'
|
|
7
|
-
description = 'high level socket and pipe
|
|
8
|
-
version = '0.0.
|
|
7
|
+
description = 'high level socket and pipe wrappers'
|
|
8
|
+
version = '0.0.3'
|
|
9
9
|
requires-python = '>=3.12'
|
|
10
10
|
keywords = ['socket', 'pipe', 'ipc', 'asyncio']
|
|
11
11
|
readme = { file = 'README.md', content-type = 'text/markdown' }
|
|
@@ -22,7 +22,7 @@ authors = [
|
|
|
22
22
|
{ name = 'Felipe A Hernandez' },
|
|
23
23
|
{ email = 'ergoithz@gmail.com' },
|
|
24
24
|
]
|
|
25
|
-
dependencies = ['typing-extensions >= 4.
|
|
25
|
+
dependencies = ['typing-extensions >= 4.6 ; python_version < "3.13"']
|
|
26
26
|
|
|
27
27
|
[tool.setuptools.package-data]
|
|
28
28
|
socketwrapper = ["py.typed"]
|
|
@@ -37,8 +37,10 @@ exclude = [
|
|
|
37
37
|
pythonVersion = '3.12'
|
|
38
38
|
|
|
39
39
|
[project.optional-dependencies]
|
|
40
|
+
msgpack = ['msgpack']
|
|
40
41
|
dev = [
|
|
41
42
|
'coverage',
|
|
43
|
+
'msgpack',
|
|
42
44
|
'ruff',
|
|
43
45
|
'wheel',
|
|
44
46
|
'yapf',
|
|
@@ -46,6 +48,7 @@ dev = [
|
|
|
46
48
|
|
|
47
49
|
[project.urls]
|
|
48
50
|
homepage = 'https://gitlab.com/ergoithz/socketwrapper'
|
|
51
|
+
repository = 'https://gitlab.com/ergoithz/socketwrapper'
|
|
49
52
|
issue-tracker = 'https://gitlab.com/ergoithz/socketwrapper/-/issues'
|
|
50
53
|
release-notes = 'https://gitlab.com/ergoithz/socketwrapper/-/releases'
|
|
51
54
|
issue-new = 'https://gitlab.com/ergoithz/socketwrapper/-/issues/new'
|