-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathworkers.py
More file actions
195 lines (150 loc) · 5.58 KB
/
workers.py
File metadata and controls
195 lines (150 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
"""
This module provides access to named
[web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
defined in `<script>` tags, and utilities for dynamically creating workers
from Python code.
Named workers are Python web workers defined in HTML with a `name` attribute
that can be referenced from the main thread or other workers. This module
provides the `workers` object for accessing named workers and the
`create_named_worker()` function for dynamically creating them.
Accessing named workers:
```html
<!-- Define a named worker -->
<script type="py" worker name="calculator">
def add(a, b):
return a + b
__export__ = ["add"]
</script>
<!-- Access from main thread -->
<script type="mpy">
from pyscript import workers
calc = await workers["calculator"]
result = await calc.add(5, 3)
print(result) # 8
</script>
```
Dynamically creating named workers:
```python
from pyscript import create_named_worker
# Create a worker from a Python file.
worker = await create_named_worker(
src="./background_tasks.py",
name="task-processor"
)
# Use the worker's exported functions.
result = await worker.process_data([1, 2, 3, 4, 5])
print(result)
```
Key features:
- Access (`await`) named workers via dictionary-like syntax.
- Dynamically create workers from Python.
- Cross-interpreter support (Pyodide and MicroPython).
Worker access is asynchronous - you must `await workers[name]` to get
a reference to the worker. This is because workers may not be ready
immediately at startup.
"""
import js
import json
from polyscript import workers as _polyscript_workers
class _ReadOnlyWorkersProxy:
"""
A read-only proxy for accessing named web workers. Use
`create_named_worker()` to create new workers found in this proxy.
This provides dictionary-like access to named workers defined in
the page. It handles differences between Pyodide and MicroPython
implementations transparently.
(See: https://github.com/pyscript/pyscript/issues/2106 for context.)
The proxy is read-only to prevent accidental modification of the
underlying workers registry. Both item access and attribute access are
supported for convenience (especially since HTML attribute names may
not be valid Python identifiers).
```python
from pyscript import workers
# Access a named worker.
my_worker = await workers["worker-name"]
result = await my_worker.some_function()
# Alternatively, if the name works, access via attribute notation.
my_worker = await workers.worker_name
result = await my_worker.some_function()
```
**This is a proxy object, not a dict**. You cannot iterate over it or
get a list of worker names. This is intentional because worker
startup timing is non-deterministic.
"""
def __getitem__(self, name):
"""
Get a named worker by `name`. It returns a promise that resolves to
the worker reference when ready.
This is useful if the underlying worker name is not a valid Python
identifier.
```python
worker = await workers["my-worker"]
```
"""
return js.Reflect.get(_polyscript_workers, name)
def __getattr__(self, name):
"""
Get a named worker as an attribute. It returns a promise that resolves
to the worker reference when ready.
This allows accessing workers via dot notation as an alternative
to bracket notation.
```python
worker = await workers.my_worker
```
"""
return js.Reflect.get(_polyscript_workers, name)
# Global workers proxy for accessing named workers.
workers = _ReadOnlyWorkersProxy()
"""Global proxy for accessing named web workers."""
async def create_named_worker(src, name, config=None, type="py"):
"""
Dynamically create a web worker with a `src` Python file, a unique
`name` and optional `config` (dict or JSON string) and `type` (`py`
for Pyodide or `mpy` for MicroPython, the default is `py`).
This function creates a new web worker by injecting a `<script>` tag into
the document. The worker will be accessible via the `workers` proxy once
it's ready.
It returns a promise that resolves to the worker reference when ready.
```python
from pyscript import create_named_worker
# Create a Pyodide worker.
worker = await create_named_worker(
src="./my_worker.py",
name="background-worker"
)
# Use the worker.
result = await worker.process_data()
# Create with standard PyScript configuration.
worker = await create_named_worker(
src="./processor.py",
name="data-processor",
config={"packages": ["numpy", "pandas"]}
)
# Use MicroPython instead.
worker = await create_named_worker(
src="./lightweight_worker.py",
name="micro-worker",
type="mpy"
)
```
!!! info
**The worker script should define** `__export__` to specify which
functions or objects are accessible from the main thread.
"""
# Create script element for the worker.
script = js.document.createElement("script")
script.type = type
script.src = src
# Mark as a worker with a name.
script.setAttribute("worker", "")
script.setAttribute("name", name)
# Add configuration if provided.
if config:
if isinstance(config, str):
config_str = config
else:
config_str = json.dumps(config)
script.setAttribute("config", config_str)
# Inject the script into the document and await the result.
js.document.body.append(script)
return await workers[name]