@pyscript/core 0.7.11 → 0.7.12
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.
- package/dist/{codemirror-BYspKCDy.js → codemirror-WbVPJuAs.js} +2 -2
- package/dist/codemirror-WbVPJuAs.js.map +1 -0
- package/dist/{codemirror_commands-BLDaEdQ6.js → codemirror_commands-BRu2f-2p.js} +2 -2
- package/dist/codemirror_commands-BRu2f-2p.js.map +1 -0
- package/dist/codemirror_lang-python-q-wuh0nL.js +2 -0
- package/dist/codemirror_lang-python-q-wuh0nL.js.map +1 -0
- package/dist/codemirror_language-DqPeLcFN.js +2 -0
- package/dist/codemirror_language-DqPeLcFN.js.map +1 -0
- package/dist/{codemirror_state-BIAL8JKm.js → codemirror_state-DWQh5Ruf.js} +2 -2
- package/dist/codemirror_state-DWQh5Ruf.js.map +1 -0
- package/dist/codemirror_view-CMXZSWgf.js +2 -0
- package/dist/codemirror_view-CMXZSWgf.js.map +1 -0
- package/dist/core-DmpFMpAn.js +4 -0
- package/dist/core-DmpFMpAn.js.map +1 -0
- package/dist/core.js +1 -1
- package/dist/{deprecations-manager-DIDxhyRq.js → deprecations-manager-HQLYNCYn.js} +2 -2
- package/dist/{deprecations-manager-DIDxhyRq.js.map → deprecations-manager-HQLYNCYn.js.map} +1 -1
- package/dist/{donkey-CLhmQOjG.js → donkey-B3F8VdSV.js} +2 -2
- package/dist/{donkey-CLhmQOjG.js.map → donkey-B3F8VdSV.js.map} +1 -1
- package/dist/{error-uzvvriog.js → error-DS_5_C5_.js} +2 -2
- package/dist/{error-uzvvriog.js.map → error-DS_5_C5_.js.map} +1 -1
- package/dist/index-DaYI1YXo.js +2 -0
- package/dist/index-DaYI1YXo.js.map +1 -0
- package/dist/{mpy-CnF17tqI.js → mpy-Bo2uW6nt.js} +2 -2
- package/dist/{mpy-CnF17tqI.js.map → mpy-Bo2uW6nt.js.map} +1 -1
- package/dist/{py-BZSSqcx3.js → py-BEf8j7L5.js} +2 -2
- package/dist/{py-BZSSqcx3.js.map → py-BEf8j7L5.js.map} +1 -1
- package/dist/{py-editor-DZ0Dxzzk.js → py-editor-C0XF2rwE.js} +2 -2
- package/dist/{py-editor-DZ0Dxzzk.js.map → py-editor-C0XF2rwE.js.map} +1 -1
- package/dist/{py-game-bqieV522.js → py-game-eWNz96mt.js} +2 -2
- package/dist/{py-game-bqieV522.js.map → py-game-eWNz96mt.js.map} +1 -1
- package/dist/{py-terminal-DYY4WN57.js → py-terminal-VtavPj1S.js} +2 -2
- package/dist/{py-terminal-DYY4WN57.js.map → py-terminal-VtavPj1S.js.map} +1 -1
- package/dist/xterm_addon-fit-DxKdSnof.js +14 -0
- package/dist/xterm_addon-fit-DxKdSnof.js.map +1 -0
- package/dist/xterm_addon-web-links-B6rWzrcs.js +14 -0
- package/dist/xterm_addon-web-links-B6rWzrcs.js.map +1 -0
- package/dist/zip-CgZGjqjF.js +2 -0
- package/dist/zip-CgZGjqjF.js.map +1 -0
- package/package.json +15 -14
- package/src/3rd-party/xterm_addon-fit.js +14 -2
- package/src/3rd-party/xterm_addon-web-links.js +14 -2
- package/src/core.js +13 -2
- package/src/stdlib/pyscript/__init__.py +100 -31
- package/src/stdlib/pyscript/context.py +198 -0
- package/src/stdlib/pyscript/display.py +211 -127
- package/src/stdlib/pyscript/events.py +191 -88
- package/src/stdlib/pyscript/fetch.py +156 -25
- package/src/stdlib/pyscript/ffi.py +132 -16
- package/src/stdlib/pyscript/flatted.py +78 -1
- package/src/stdlib/pyscript/fs.py +205 -49
- package/src/stdlib/pyscript/media.py +210 -50
- package/src/stdlib/pyscript/storage.py +214 -27
- package/src/stdlib/pyscript/util.py +28 -7
- package/src/stdlib/pyscript/web.py +1079 -881
- package/src/stdlib/pyscript/websocket.py +252 -45
- package/src/stdlib/pyscript/workers.py +176 -27
- package/src/stdlib/pyscript.js +13 -13
- package/types/stdlib/pyscript.d.ts +1 -1
- package/dist/codemirror-BYspKCDy.js.map +0 -1
- package/dist/codemirror_commands-BLDaEdQ6.js.map +0 -1
- package/dist/codemirror_lang-python-CkOVBHci.js +0 -2
- package/dist/codemirror_lang-python-CkOVBHci.js.map +0 -1
- package/dist/codemirror_language-DOkvasqm.js +0 -2
- package/dist/codemirror_language-DOkvasqm.js.map +0 -1
- package/dist/codemirror_state-BIAL8JKm.js.map +0 -1
- package/dist/codemirror_view-Bt4sLgyA.js +0 -2
- package/dist/codemirror_view-Bt4sLgyA.js.map +0 -1
- package/dist/core-PTfg6inS.js +0 -4
- package/dist/core-PTfg6inS.js.map +0 -1
- package/dist/index-jZ1aOVVJ.js +0 -2
- package/dist/index-jZ1aOVVJ.js.map +0 -1
- package/dist/xterm_addon-fit--gyF3PcZ.js +0 -2
- package/dist/xterm_addon-fit--gyF3PcZ.js.map +0 -1
- package/dist/xterm_addon-web-links-D95xh2la.js +0 -2
- package/dist/xterm_addon-web-links-D95xh2la.js.map +0 -1
- package/dist/zip-pccs084i.js +0 -2
- package/dist/zip-pccs084i.js.map +0 -1
- package/src/stdlib/pyscript/magic_js.py +0 -84
|
@@ -1,88 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides a Pythonic wrapper around the browser's
|
|
3
|
+
[WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket),
|
|
4
|
+
enabling two-way communication with WebSocket servers.
|
|
5
|
+
|
|
6
|
+
Use this for real-time applications:
|
|
7
|
+
|
|
8
|
+
- Pythonic interface to browser WebSockets.
|
|
9
|
+
- Automatic handling of async event handlers.
|
|
10
|
+
- Support for receiving text (`str`) and binary (`memoryview`) data.
|
|
11
|
+
- Support for sending text (`str`) and binary (`bytes` and `bytearray`) data.
|
|
12
|
+
- Compatible with Pyodide and MicroPython.
|
|
13
|
+
- Works in webworker contexts.
|
|
14
|
+
- Naming deliberately follows the JavaScript WebSocket API closely for
|
|
15
|
+
familiarity.
|
|
16
|
+
|
|
17
|
+
See the Python docs for
|
|
18
|
+
[an explanation of memoryview](https://docs.python.org/3/library/stdtypes.html#memoryview).
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from pyscript import WebSocket
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def on_open(event):
|
|
26
|
+
print("Connected!")
|
|
27
|
+
ws.send("Hello server")
|
|
28
|
+
|
|
29
|
+
def on_message(event):
|
|
30
|
+
print(f"Received: {event.data}")
|
|
31
|
+
|
|
32
|
+
def on_close(event):
|
|
33
|
+
print("Connection closed")
|
|
34
|
+
|
|
35
|
+
ws = WebSocket(url="ws://localhost:8080/")
|
|
36
|
+
ws.onopen = on_open
|
|
37
|
+
ws.onmessage = on_message
|
|
38
|
+
ws.onclose = on_close
|
|
39
|
+
```
|
|
40
|
+
"""
|
|
41
|
+
|
|
1
42
|
import js
|
|
2
43
|
from pyscript.ffi import create_proxy
|
|
3
44
|
from pyscript.util import as_bytearray, is_awaitable
|
|
4
45
|
|
|
5
|
-
code = "code"
|
|
6
|
-
protocols = "protocols"
|
|
7
|
-
reason = "reason"
|
|
8
|
-
methods = ["onclose", "onerror", "onmessage", "onopen"]
|
|
9
46
|
|
|
47
|
+
def _attach_event_handler(websocket, handler_name, handler_function):
|
|
48
|
+
"""
|
|
49
|
+
Given a `websocket`, and `handler_name`, attach the `handler_function`
|
|
50
|
+
to the `WebSocket` instance, handling both synchronous and asynchronous
|
|
51
|
+
handler functions.
|
|
10
52
|
|
|
11
|
-
|
|
12
|
-
|
|
53
|
+
Creates a JavaScript proxy for the handler and wraps async handlers
|
|
54
|
+
appropriately. Handles the `WebSocketEvent` wrapping for all handlers.
|
|
55
|
+
"""
|
|
56
|
+
if is_awaitable(handler_function):
|
|
13
57
|
|
|
14
|
-
|
|
58
|
+
async def async_wrapper(event):
|
|
59
|
+
await handler_function(WebSocketEvent(event))
|
|
15
60
|
|
|
16
|
-
|
|
17
|
-
|
|
61
|
+
wrapped_handler = create_proxy(async_wrapper)
|
|
62
|
+
else:
|
|
63
|
+
wrapped_handler = create_proxy(
|
|
64
|
+
lambda event: handler_function(WebSocketEvent(event))
|
|
65
|
+
)
|
|
66
|
+
# Note: Direct assignment (websocket[handler_name]) fails in Pyodide.
|
|
67
|
+
setattr(websocket, handler_name, wrapped_handler)
|
|
18
68
|
|
|
19
|
-
m = wrapper
|
|
20
69
|
|
|
21
|
-
|
|
22
|
-
|
|
70
|
+
class WebSocketEvent:
|
|
71
|
+
"""
|
|
72
|
+
A read-only wrapper for
|
|
73
|
+
[WebSocket event objects](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent).
|
|
23
74
|
|
|
24
|
-
|
|
25
|
-
|
|
75
|
+
This class wraps browser WebSocket events and provides convenient access
|
|
76
|
+
to event properties. It handles the conversion of binary data from
|
|
77
|
+
JavaScript typed arrays to Python bytes-like objects.
|
|
26
78
|
|
|
79
|
+
The most commonly used property is `event.data`, which contains the
|
|
80
|
+
message data for "message" events.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
def on_message(event): # The event is a WebSocketEvent instance.
|
|
84
|
+
# For text messages.
|
|
85
|
+
if isinstance(event.data, str):
|
|
86
|
+
print(f"Text: {event.data}")
|
|
87
|
+
else:
|
|
88
|
+
# For binary messages.
|
|
89
|
+
print(f"Binary: {len(event.data)} bytes")
|
|
90
|
+
```
|
|
91
|
+
"""
|
|
27
92
|
|
|
28
|
-
class EventMessage:
|
|
29
93
|
def __init__(self, event):
|
|
94
|
+
"""
|
|
95
|
+
Create a WebSocketEvent wrapper from an underlying JavaScript
|
|
96
|
+
`event`.
|
|
97
|
+
"""
|
|
30
98
|
self._event = event
|
|
31
99
|
|
|
32
100
|
def __getattr__(self, attr):
|
|
33
|
-
|
|
101
|
+
"""
|
|
102
|
+
Get an attribute `attr` from the underlying event object.
|
|
34
103
|
|
|
104
|
+
Handles special conversion of binary data from JavaScript typed
|
|
105
|
+
arrays to Python `memoryview` objects.
|
|
106
|
+
"""
|
|
107
|
+
value = getattr(self._event, attr)
|
|
35
108
|
if attr == "data" and not isinstance(value, str):
|
|
36
109
|
if hasattr(value, "to_py"):
|
|
110
|
+
# Pyodide - convert JavaScript typed array to Python.
|
|
37
111
|
return value.to_py()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
112
|
+
else:
|
|
113
|
+
# MicroPython - manually convert JS ArrayBuffer.
|
|
114
|
+
return memoryview(as_bytearray(value))
|
|
41
115
|
return value
|
|
42
116
|
|
|
43
117
|
|
|
44
118
|
class WebSocket:
|
|
119
|
+
"""
|
|
120
|
+
This class provides a Python-friendly interface to WebSocket connections,
|
|
121
|
+
handling communication with WebSocket servers. It supports both text and
|
|
122
|
+
binary data transmission.
|
|
123
|
+
|
|
124
|
+
Access the underlying WebSocket methods and properties directly if needed.
|
|
125
|
+
However, the wrapper provides a more Pythonic API. If you need to work
|
|
126
|
+
with the raw JavaScript WebSocket instance, you can access it via the
|
|
127
|
+
`_js_websocket` attribute.
|
|
128
|
+
|
|
129
|
+
Using textual (`str`) data:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from pyscript import WebSocket
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# Create WebSocket with handlers as arguments.
|
|
136
|
+
def handle_message(event):
|
|
137
|
+
print(f"Got: {event.data}")
|
|
138
|
+
|
|
139
|
+
ws = WebSocket(
|
|
140
|
+
url="ws://echo.websocket.org/",
|
|
141
|
+
onmessage=handle_message
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Or assign handlers after creation.
|
|
145
|
+
def handle_open(event):
|
|
146
|
+
ws.send("Hello!")
|
|
147
|
+
|
|
148
|
+
ws.onopen = handle_open
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Using binary (`memoryview`) data:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
def handle_message(event):
|
|
155
|
+
if isinstance(event.data, str):
|
|
156
|
+
print(f"Text: {event.data}")
|
|
157
|
+
else:
|
|
158
|
+
# Binary data as memoryview.
|
|
159
|
+
print(f"Binary: {len(event.data)} bytes")
|
|
160
|
+
|
|
161
|
+
ws = WebSocket(url="ws://example.com/", onmessage=handle_message)
|
|
162
|
+
|
|
163
|
+
# Send binary data.
|
|
164
|
+
data = bytearray([0x01, 0x02, 0x03])
|
|
165
|
+
ws.send(data)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Read more about Python's
|
|
169
|
+
[`memoryview` here](https://docs.python.org/3/library/stdtypes.html#memoryview).
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
# WebSocket ready state constants.
|
|
45
173
|
CONNECTING = 0
|
|
46
174
|
OPEN = 1
|
|
47
175
|
CLOSING = 2
|
|
48
176
|
CLOSED = 3
|
|
49
177
|
|
|
50
|
-
def __init__(self, **
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
178
|
+
def __init__(self, url, protocols=None, **handlers):
|
|
179
|
+
"""
|
|
180
|
+
Create a new WebSocket connection from the given `url` (`ws://` or
|
|
181
|
+
`wss://`). Optionally specify `protocols` (a string or a list of
|
|
182
|
+
protocol strings) and event handlers (`onopen`, `onmessage`, etc.) as
|
|
183
|
+
keyword arguments.
|
|
184
|
+
|
|
185
|
+
These arguments and naming conventions mirror those of the
|
|
186
|
+
[underlying JavaScript WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
|
187
|
+
for familiarity.
|
|
56
188
|
|
|
57
|
-
|
|
58
|
-
|
|
189
|
+
If you need access to the underlying JavaScript WebSocket instance,
|
|
190
|
+
you can get it via the `_js_websocket` attribute.
|
|
59
191
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
192
|
+
```python
|
|
193
|
+
# Basic connection.
|
|
194
|
+
ws = WebSocket(url="ws://localhost:8080/")
|
|
195
|
+
|
|
196
|
+
# With protocol.
|
|
197
|
+
ws = WebSocket(
|
|
198
|
+
url="wss://example.com/socket",
|
|
199
|
+
protocols="chat"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# With handlers.
|
|
203
|
+
ws = WebSocket(
|
|
204
|
+
url="ws://localhost:8080/",
|
|
205
|
+
onopen=lambda e: print("Connected"),
|
|
206
|
+
onmessage=lambda e: print(e.data)
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
"""
|
|
210
|
+
# Create underlying JavaScript WebSocket.
|
|
211
|
+
if protocols:
|
|
212
|
+
js_websocket = js.WebSocket.new(url, protocols)
|
|
213
|
+
else:
|
|
214
|
+
js_websocket = js.WebSocket.new(url)
|
|
215
|
+
# Set binary type to arraybuffer for easier Python handling.
|
|
216
|
+
js_websocket.binaryType = "arraybuffer"
|
|
217
|
+
# Store the underlying WebSocket.
|
|
218
|
+
# Use object.__setattr__ to bypass our custom __setattr__.
|
|
219
|
+
object.__setattr__(self, "_js_websocket", js_websocket)
|
|
220
|
+
# Attach any event handlers passed as keyword arguments.
|
|
221
|
+
for handler_name, handler in handlers.items():
|
|
222
|
+
setattr(self, handler_name, handler)
|
|
63
223
|
|
|
64
224
|
def __getattr__(self, attr):
|
|
65
|
-
|
|
225
|
+
"""
|
|
226
|
+
Get an attribute `attr` from the underlying WebSocket.
|
|
227
|
+
|
|
228
|
+
This allows transparent access to WebSocket properties like
|
|
229
|
+
`readyState`, `url`, `bufferedAmount`, etc.
|
|
230
|
+
"""
|
|
231
|
+
return getattr(self._js_websocket, attr)
|
|
66
232
|
|
|
67
233
|
def __setattr__(self, attr, value):
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
else:
|
|
71
|
-
setattr(self._ws, attr, value)
|
|
234
|
+
"""
|
|
235
|
+
Set an attribute `attr` on the WebSocket to the given `value`.
|
|
72
236
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
237
|
+
Event handler attributes (`onopen`, `onmessage`, etc.) are specially
|
|
238
|
+
handled to create proper proxies. Other attributes are set on the
|
|
239
|
+
underlying WebSocket directly.
|
|
240
|
+
"""
|
|
241
|
+
if attr in ["onclose", "onerror", "onmessage", "onopen"]:
|
|
242
|
+
_attach_event_handler(self._js_websocket, attr, value)
|
|
78
243
|
else:
|
|
79
|
-
self.
|
|
244
|
+
setattr(self._js_websocket, attr, value)
|
|
80
245
|
|
|
81
246
|
def send(self, data):
|
|
247
|
+
"""
|
|
248
|
+
Send `data` through the WebSocket.
|
|
249
|
+
|
|
250
|
+
Accepts both text (`str`) and binary data (`bytes`, `bytearray`, etc.).
|
|
251
|
+
Binary data is automatically converted to a JavaScript `Uint8Array`.
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
# Send text.
|
|
255
|
+
ws.send("Hello server!")
|
|
256
|
+
|
|
257
|
+
# Send binary.
|
|
258
|
+
ws.send(bytes([1, 2, 3, 4]))
|
|
259
|
+
ws.send(bytearray([5, 6, 7, 8]))
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
!!! warning
|
|
263
|
+
|
|
264
|
+
The WebSocket **must be in the OPEN state to send data**.
|
|
265
|
+
"""
|
|
82
266
|
if isinstance(data, str):
|
|
83
|
-
self.
|
|
267
|
+
self._js_websocket.send(data)
|
|
84
268
|
else:
|
|
85
269
|
buffer = js.Uint8Array.new(len(data))
|
|
86
|
-
for
|
|
87
|
-
buffer[
|
|
88
|
-
self.
|
|
270
|
+
for index, byte_value in enumerate(data):
|
|
271
|
+
buffer[index] = byte_value
|
|
272
|
+
self._js_websocket.send(buffer)
|
|
273
|
+
|
|
274
|
+
def close(self, code=None, reason=None):
|
|
275
|
+
"""
|
|
276
|
+
Close the WebSocket connection. Optionally specify a `code` (`int`)
|
|
277
|
+
and a `reason` (`str`) for closing the connection.
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
# Normal close.
|
|
281
|
+
ws.close()
|
|
282
|
+
|
|
283
|
+
# Close with code and reason.
|
|
284
|
+
ws.close(code=1000, reason="Task completed")
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Usage and values for `code` and `reasons`
|
|
288
|
+
[are explained here](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close).
|
|
289
|
+
"""
|
|
290
|
+
if code and reason:
|
|
291
|
+
self._js_websocket.close(code, reason)
|
|
292
|
+
elif code:
|
|
293
|
+
self._js_websocket.close(code)
|
|
294
|
+
else:
|
|
295
|
+
self._js_websocket.close()
|
|
@@ -1,45 +1,194 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"""
|
|
2
|
+
This module provides access to named
|
|
3
|
+
[web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
|
|
4
|
+
defined in `<script>` tags, and utilities for dynamically creating workers
|
|
5
|
+
from Python code.
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
Named workers are Python web workers defined in HTML with a `name` attribute
|
|
8
|
+
that can be referenced from the main thread or other workers. This module
|
|
9
|
+
provides the `workers` object for accessing named workers and the
|
|
10
|
+
`create_named_worker()` function for dynamically creating them.
|
|
5
11
|
|
|
12
|
+
Accessing named workers:
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
```html
|
|
15
|
+
<!-- Define a named worker -->
|
|
16
|
+
<script type="py" worker name="calculator">
|
|
17
|
+
def add(a, b):
|
|
18
|
+
return a + b
|
|
9
19
|
|
|
20
|
+
__export__ = ["add"]
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<!-- Access from main thread -->
|
|
24
|
+
<script type="mpy">
|
|
25
|
+
from pyscript import workers
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
calc = await workers["calculator"]
|
|
29
|
+
result = await calc.add(5, 3)
|
|
30
|
+
print(result) # 8
|
|
31
|
+
</script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Dynamically creating named workers:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from pyscript import create_named_worker
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Create a worker from a Python file.
|
|
41
|
+
worker = await create_named_worker(
|
|
42
|
+
src="./background_tasks.py",
|
|
43
|
+
name="task-processor"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Use the worker's exported functions.
|
|
47
|
+
result = await worker.process_data([1, 2, 3, 4, 5])
|
|
48
|
+
print(result)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Key features:
|
|
52
|
+
- Access (`await`) named workers via dictionary-like syntax.
|
|
53
|
+
- Dynamically create workers from Python.
|
|
54
|
+
- Cross-interpreter support (Pyodide and MicroPython).
|
|
55
|
+
|
|
56
|
+
Worker access is asynchronous - you must `await workers[name]` to get
|
|
57
|
+
a reference to the worker. This is because workers may not be ready
|
|
58
|
+
immediately at startup.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
import js
|
|
62
|
+
import json
|
|
63
|
+
from polyscript import workers as _polyscript_workers
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class _ReadOnlyWorkersProxy:
|
|
67
|
+
"""
|
|
68
|
+
A read-only proxy for accessing named web workers. Use
|
|
69
|
+
`create_named_worker()` to create new workers found in this proxy.
|
|
70
|
+
|
|
71
|
+
This provides dictionary-like access to named workers defined in
|
|
72
|
+
the page. It handles differences between Pyodide and MicroPython
|
|
73
|
+
implementations transparently.
|
|
74
|
+
|
|
75
|
+
(See: https://github.com/pyscript/pyscript/issues/2106 for context.)
|
|
76
|
+
|
|
77
|
+
The proxy is read-only to prevent accidental modification of the
|
|
78
|
+
underlying workers registry. Both item access and attribute access are
|
|
79
|
+
supported for convenience (especially since HTML attribute names may
|
|
80
|
+
not be valid Python identifiers).
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from pyscript import workers
|
|
84
|
+
|
|
85
|
+
# Access a named worker.
|
|
86
|
+
my_worker = await workers["worker-name"]
|
|
87
|
+
result = await my_worker.some_function()
|
|
88
|
+
|
|
89
|
+
# Alternatively, if the name works, access via attribute notation.
|
|
90
|
+
my_worker = await workers.worker_name
|
|
91
|
+
result = await my_worker.some_function()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**This is a proxy object, not a dict**. You cannot iterate over it or
|
|
95
|
+
get a list of worker names. This is intentional because worker
|
|
96
|
+
startup timing is non-deterministic.
|
|
97
|
+
"""
|
|
10
98
|
|
|
11
|
-
# this solves an inconsistency between Pyodide and MicroPython
|
|
12
|
-
# @see https://github.com/pyscript/pyscript/issues/2106
|
|
13
|
-
class _ReadOnlyProxy:
|
|
14
99
|
def __getitem__(self, name):
|
|
15
|
-
|
|
100
|
+
"""
|
|
101
|
+
Get a named worker by `name`. It returns a promise that resolves to
|
|
102
|
+
the worker reference when ready.
|
|
103
|
+
|
|
104
|
+
This is useful if the underlying worker name is not a valid Python
|
|
105
|
+
identifier.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
worker = await workers["my-worker"]
|
|
109
|
+
```
|
|
110
|
+
"""
|
|
111
|
+
return js.Reflect.get(_polyscript_workers, name)
|
|
16
112
|
|
|
17
113
|
def __getattr__(self, name):
|
|
18
|
-
|
|
114
|
+
"""
|
|
115
|
+
Get a named worker as an attribute. It returns a promise that resolves
|
|
116
|
+
to the worker reference when ready.
|
|
19
117
|
|
|
118
|
+
This allows accessing workers via dot notation as an alternative
|
|
119
|
+
to bracket notation.
|
|
20
120
|
|
|
21
|
-
|
|
121
|
+
```python
|
|
122
|
+
worker = await workers.my_worker
|
|
123
|
+
```
|
|
124
|
+
"""
|
|
125
|
+
return js.Reflect.get(_polyscript_workers, name)
|
|
22
126
|
|
|
23
127
|
|
|
24
|
-
|
|
25
|
-
|
|
128
|
+
# Global workers proxy for accessing named workers.
|
|
129
|
+
workers = _ReadOnlyWorkersProxy()
|
|
130
|
+
"""Global proxy for accessing named web workers."""
|
|
26
131
|
|
|
27
|
-
if not src:
|
|
28
|
-
msg = "Named workers require src"
|
|
29
|
-
raise ValueError(msg)
|
|
30
132
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
133
|
+
async def create_named_worker(src, name, config=None, type="py"):
|
|
134
|
+
"""
|
|
135
|
+
Dynamically create a web worker with a `src` Python file, a unique
|
|
136
|
+
`name` and optional `config` (dict or JSON string) and `type` (`py`
|
|
137
|
+
for Pyodide or `mpy` for MicroPython, the default is `py`).
|
|
34
138
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
s.
|
|
38
|
-
_set(s, "worker")
|
|
39
|
-
_set(s, "name", name)
|
|
139
|
+
This function creates a new web worker by injecting a `<script>` tag into
|
|
140
|
+
the document. The worker will be accessible via the `workers` proxy once
|
|
141
|
+
it's ready.
|
|
40
142
|
|
|
41
|
-
|
|
42
|
-
|
|
143
|
+
It returns a promise that resolves to the worker reference when ready.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from pyscript import create_named_worker
|
|
43
147
|
|
|
44
|
-
|
|
148
|
+
|
|
149
|
+
# Create a Pyodide worker.
|
|
150
|
+
worker = await create_named_worker(
|
|
151
|
+
src="./my_worker.py",
|
|
152
|
+
name="background-worker"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Use the worker.
|
|
156
|
+
result = await worker.process_data()
|
|
157
|
+
|
|
158
|
+
# Create with standard PyScript configuration.
|
|
159
|
+
worker = await create_named_worker(
|
|
160
|
+
src="./processor.py",
|
|
161
|
+
name="data-processor",
|
|
162
|
+
config={"packages": ["numpy", "pandas"]}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Use MicroPython instead.
|
|
166
|
+
worker = await create_named_worker(
|
|
167
|
+
src="./lightweight_worker.py",
|
|
168
|
+
name="micro-worker",
|
|
169
|
+
type="mpy"
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
!!! info
|
|
174
|
+
|
|
175
|
+
**The worker script should define** `__export__` to specify which
|
|
176
|
+
functions or objects are accessible from the main thread.
|
|
177
|
+
"""
|
|
178
|
+
# Create script element for the worker.
|
|
179
|
+
script = js.document.createElement("script")
|
|
180
|
+
script.type = type
|
|
181
|
+
script.src = src
|
|
182
|
+
# Mark as a worker with a name.
|
|
183
|
+
script.setAttribute("worker", "")
|
|
184
|
+
script.setAttribute("name", name)
|
|
185
|
+
# Add configuration if provided.
|
|
186
|
+
if config:
|
|
187
|
+
if isinstance(config, str):
|
|
188
|
+
config_str = config
|
|
189
|
+
else:
|
|
190
|
+
config_str = json.dumps(config)
|
|
191
|
+
script.setAttribute("config", config_str)
|
|
192
|
+
# Inject the script into the document and await the result.
|
|
193
|
+
js.document.body.append(script)
|
|
45
194
|
return await workers[name]
|