Skip to content

Commit a0fd3a5

Browse files
committed
Finish FAQ, add web sockets, web worker examples and bumped version to 2024.5.1
1 parent 6eeaf04 commit a0fd3a5

9 files changed

+505
-138
lines changed

docs/beginning-pyscript.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ module in the document's `<head>` tag:
112112
<meta charset="utf-8" />
113113
<meta name="viewport" content="width=device-width,initial-scale=1" />
114114
<title>🦜 Polyglot - Piratical PyScript</title>
115-
<link rel="stylesheet" href="https://pyscript.net/releases/2024.4.2/core.css">
116-
<script type="module" src="https://pyscript.net/releases/2024.4.2/core.js"></script>
115+
<link rel="stylesheet" href="https://pyscript.net/releases/2024.5.1/core.css">
116+
<script type="module" src="https://pyscript.net/releases/2024.5.1/core.js"></script>
117117
</head>
118118
<body>
119119

@@ -163,8 +163,8 @@ In the end, our HTML should look like this:
163163
<meta charset="utf-8" />
164164
<meta name="viewport" content="width=device-width,initial-scale=1" />
165165
<title>🦜 Polyglot - Piratical PyScript</title>
166-
<link rel="stylesheet" href="https://pyscript.net/releases/2024.4.2/core.css">
167-
<script type="module" src="https://pyscript.net/releases/2024.4.2/core.js"></script>
166+
<link rel="stylesheet" href="https://pyscript.net/releases/2024.5.1/core.css">
167+
<script type="module" src="https://pyscript.net/releases/2024.5.1/core.js"></script>
168168
</head>
169169
<body>
170170
<h1>Polyglot 🦜 💬 🇬🇧 ➡️ 🏴‍☠️</h1>

docs/faq.md

Lines changed: 257 additions & 110 deletions
Large diffs are not rendered by default.

docs/user-guide/builtins.md

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ On a worker thread, this object is a proxy for the web page's
4141

4242
### `pyscript.document`
4343

44-
On both main and worker threads, this object is a proxy for the the web page's
44+
On both main and worker threads, this object is a proxy for the web page's
4545
[document object](https://developer.mozilla.org/en-US/docs/Web/API/Document).
4646
The `document` is a representation of the
4747
[DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_object_model/Using_the_Document_Object_Model)
@@ -257,6 +257,121 @@ result = await fetch("https://example.com", method="POST", body="HELLO").text()
257257
bug). However, you could use a pass-through proxy service to get around
258258
this limitation (i.e. the proxy service makes the call on your behalf).
259259

260+
### `pyscript.WebSocket`
261+
262+
If a `pyscript.fetch` results in a call and response HTTP interaction with a
263+
web server, the `pyscript.Websocket` class provides a way to use
264+
[websockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
265+
for two-way sending and receiving of data via a long term connection with a
266+
web server.
267+
268+
PyScript's implementation, available in both the main thread and a web worker,
269+
closely follows the browser's own
270+
[WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) class.
271+
272+
This class accepts the following named arguments:
273+
274+
* A `url` pointing at the _ws_ or _wss_ address. E.g.:
275+
`WebSocket(url="ws://localhost:5037/")`
276+
* Some `protocols`, an optional string or a list of strings as
277+
[described here](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#parameters).
278+
279+
The `WebSocket` class also provides these convenient static constants:
280+
281+
* `WebSocket.CONNECTING` (`0`) - the `ws.readyState` value when a web socket
282+
has just been created.
283+
* `WebSocket.OPEN` (`1`) - the `ws.readyState` value once the socket is open.
284+
* `WebSocket.CLOSING` (`2`) - the `ws.readyState` after `ws.close()` is
285+
explicitly invoked to stop the connection.
286+
* `WebSocket.CLOSED` (`3`) - the `ws.readyState` once closed.
287+
288+
A `WebSocket` instance has only 2 methods:
289+
290+
* `ws.send(data)` - where `data` is either a string or a Python buffer,
291+
automatically converted into a JavaScript typed array. This sends data via
292+
the socket to the connected web server.
293+
* `ws.close(code=0, reason="because")` - which optionally accepts `code` and
294+
`reason` as named arguments to signal some specific status or cause for
295+
closing the web socket. Otherwise `ws.close()` works with the default
296+
standard values.
297+
298+
A `WebSocket` instance also has the fields that the JavaScript
299+
`WebSocket` instance will have:
300+
301+
* [binaryType](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType) -
302+
the type of binary data being received over the WebSocket connection.
303+
* [bufferedAmount](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/bufferedAmount) -
304+
a read-only property that returns the number of bytes of data that have been
305+
queued using calls to `send()` but not yet transmitted to the network.
306+
* [extensions](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/extensions) -
307+
a read-only property that returns the extensions selected by the server.
308+
* [protocol](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/protocol) -
309+
a read-only property that returns the name of the sub-protocol the server
310+
selected.
311+
* [readyState](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState) -
312+
a read-only property that returns the current state of the WebSocket
313+
connection as one of the `WebSocket` static constants (`CONNECTING`, `OPEN`,
314+
etc...).
315+
* [url](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/url) -
316+
a read-only property that returns the absolute URL of the `WebSocket`
317+
instance.
318+
319+
A `WebSocket` instance can have the following listeners. Directly attach
320+
handler functions to them. Such functions will always receive a single
321+
`event` object.
322+
323+
* [onclose](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close_event) -
324+
fired when the `WebSocket`'s connection is closed.
325+
* [onerror](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/error_event) -
326+
fired when the connection is closed due to an error.
327+
* [onmessage](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/message_event) -
328+
fired when data is received via the `WebSocket`. If the `event.data` is a
329+
JavaScript typed array instead of a string, the reference it will point
330+
directly to a _memoryview_ of the underlying `bytearray` data.
331+
* [onopen](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/open_event) -
332+
fired when the connection is opened.
333+
334+
The following code demonstrates a `pyscript.WebSocket` in action.
335+
336+
```html
337+
<script type="mpy" worker>
338+
from pyscript import WebSocket
339+
340+
def onopen(event):
341+
print(event.type)
342+
ws.send("hello")
343+
344+
def onmessage(event):
345+
print(event.type, event.data)
346+
ws.close()
347+
348+
def onclose(event):
349+
print(event.type)
350+
351+
ws = WebSocket(url="ws://localhost:5037/")
352+
ws.onopen = onopen
353+
ws.onmessage = onmessage
354+
ws.onclose = onclose
355+
</script>
356+
```
357+
358+
!!! info
359+
360+
It's also possible to pass in any handler functions as named arguments when
361+
you instantiate the `pyscript.WebSocket` class:
362+
363+
```python
364+
from pyscript import WebSocket
365+
366+
367+
def onmessage(event):
368+
print(event.type, event.data)
369+
ws.close()
370+
371+
372+
ws = WebSocket(url="ws://example.com/socket", onmessage=onmessage)
373+
```
374+
260375
### `pyscript.ffi.to_js`
261376

262377
A utility function to convert Python references into their JavaScript
@@ -343,6 +458,25 @@ an `id`, that `id` will be returned.
343458
Then use the standard `document.getElementById(script_id)` function to
344459
return a reference to it in your code.
345460

461+
### `pyscript.config`
462+
463+
A Python dictionary representing the configuration for the interpreter.
464+
465+
```python title="Reading the current configuration."
466+
from pyscript import config
467+
468+
469+
# It's just a dict.
470+
print(config.get("files"))
471+
```
472+
473+
!!! warning
474+
475+
Changing the `config` dictionary at runtime has no effect on the actual
476+
configuration.
477+
478+
It's just a convenience to **read the configuration** at run time.
479+
346480
### `pyscript.HTML`
347481

348482
A class to wrap generic content and display it as un-escaped HTML on the page.
@@ -379,8 +513,9 @@ A class used to instantiate a new worker from within Python.
379513
Sometimes we disambiguate between interpreters through naming conventions
380514
(e.g. `py` or `mpy`).
381515

382-
However, it is always `PyWorker` and the desired interpreter is specified
383-
via a `type` option which must be either `micropython` or `pyodide`.
516+
However, this class is always `PyWorker` and **the desired interpreter
517+
MUST be specified via a `type` option**. Valid values for the type of
518+
interpreter are either `micropython` or `pyodide`.
384519

385520
The following fragments demonstrate how to evaluate the file `worker.py` on a
386521
new worker from within Python.
@@ -400,7 +535,7 @@ print("awake")
400535
```python title="main.py - starts a new worker in Python."
401536
from pyscript import PyWorker
402537

403-
# type can be either `micropython` or `pyodide`
538+
# type MUST be either `micropython` or `pyodide`
404539
PyWorker("worker.py", type="micropython")
405540
```
406541

docs/user-guide/configuration.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,18 @@ So long as you don't cause a name collision with the built-in option names then
440440
you are free to use any valid data structure that works with both TOML and JSON
441441
to express your configuration needs.
442442

443-
**TODO: explain how to programmatically get access to an object representing
444-
the config.**
443+
Access the current configuration via `pyscript.config`, a Python `dict`
444+
representing the configuration:
445445

446+
```python title="Reading the current configuration."
447+
from pyscript import config
448+
449+
450+
# It's just a dict.
451+
print(config.get("files"))
452+
```
453+
454+
!!! note
455+
456+
Changing the `config` dictionary at runtime doesn't change the actual
457+
configuration.

docs/user-guide/first-steps.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ CSS:
2020
<meta charset="UTF-8">
2121
<meta name="viewport" content="width=device-width,initial-scale=1.0">
2222
<!-- PyScript CSS -->
23-
<link rel="stylesheet" href="https://pyscript.net/releases/2024.4.2/core.css">
23+
<link rel="stylesheet" href="https://pyscript.net/releases/2024.5.1/core.css">
2424
<!-- This script tag bootstraps PyScript -->
25-
<script type="module" src="https://pyscript.net/releases/2024.4.2/core.js"></script>
25+
<script type="module" src="https://pyscript.net/releases/2024.5.1/core.js"></script>
2626
</head>
2727
<body>
2828
<!-- your code goes here... -->

docs/user-guide/plugins.md

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Plugins
22

3-
PyScript offers a plugin API to extend its functionality. It means you can add
4-
new features and distribute them as plugins.
3+
PyScript offers a plugin API _so anyone can extend its functionality and
4+
share their modifications_.
55

66
PyScript only supports plugins written in Javascript (although causing the
7-
evaluation of bespoke Python code can be a part of this process). The plugin's
7+
evaluation of bespoke Python code can be a part of such plugins). The plugin's
88
JavaScript code should be included on the web page via a
99
`<script type="module">` tag **before PyScript is included in the web page**.
1010

@@ -19,7 +19,7 @@ PyScript emits events that capture the beginning or the end of specific stages
1919
in the application's life. No matter if code is running in the main thread or
2020
on a web worker, the exact same sequence of steps takes place:
2121

22-
* **ready** - the browser recognized the PyScript `script` or tag and the
22+
* **ready** - the browser recognizes the PyScript `script` or tag, and the
2323
associated Python interpreter is ready to work. A JavaScript callback can
2424
be used to instrument the interpreter before anything else happens.
2525
* **before run** - some Python code is about to be evaluated. A JavaScript
@@ -53,14 +53,22 @@ lifecycle events. When such events happen, your code will be called by
5353
PyScript.
5454

5555
The available hooks depend upon where your code is running: on the browser's
56-
(blocking) main thread or on a (non-blocking) web worker.
56+
(blocking) main thread or on a (non-blocking) web worker. These are defined via
57+
the `hooks.main` and `hooks.worker` namespaces in JavaScript.
5758

5859

5960
#### Main Hooks
6061

61-
Callbacks registered via hooks on the main thread will receive a wrapper of
62-
the interpreter with its utilities, along with a reference to the element on
63-
the page from where the code was derived.
62+
Callbacks registered via hooks on the main thread will usually receive a
63+
wrapper of the interpreter with its unique-to-that-interpreter utilities, along
64+
with a reference to the element on the page from where the code was derived.
65+
66+
Please refer to the documentation for the interpreter you're using (Pyodide or
67+
MicroPython) to learn about its implementation details and the potential
68+
capabilities made available via the wrapper.
69+
70+
Callbacks whose purpose is simply to run raw contextual Python code, don't
71+
receive interpreter or element arguments.
6472

6573
This table lists all possible, **yet optional**, hooks a plugin can register on
6674
the main thread:
@@ -91,7 +99,7 @@ For example, this will work because all references are contained within the
9199
registered function:
92100

93101
```js
94-
import { hooks } from "https://pyscript.net/releases/2024.4.2/core.js";
102+
import { hooks } from "https://pyscript.net/releases/2024.5.1/core.js";
95103

96104
hooks.worker.onReady.add(() => {
97105
// NOT suggested, just an example!
@@ -105,7 +113,7 @@ hooks.worker.onReady.add(() => {
105113
However, due to the outer reference to the variable `i`, this will fail:
106114

107115
```js
108-
import { hooks } from "https://pyscript.net/releases/2024.4.2/core.js";
116+
import { hooks } from "https://pyscript.net/releases/2024.5.1/core.js";
109117

110118
// NO NO NO NO NO! ☠️
111119
let i = 0;
@@ -138,7 +146,7 @@ the page.
138146

139147
```js title="log.js - a plugin that simply logs to the console."
140148
// import the hooks from PyScript first...
141-
import { hooks } from "https://pyscript.net/releases/2024.4.2/core.js";
149+
import { hooks } from "https://pyscript.net/releases/2024.5.1/core.js";
142150

143151
// The `hooks.main` attribute defines plugins that run on the main thread.
144152
hooks.main.onReady.add((wrap, element) => {
@@ -188,8 +196,8 @@ hooks.worker.onAfterRun.add(() => {
188196
<!-- JS plugins should be available before PyScript bootstraps -->
189197
<script type="module" src="./log.js"></script>
190198
<!-- PyScript -->
191-
<link rel="stylesheet" href="https://pyscript.net/releases/2024.4.2/core.css">
192-
<script type="module" src="https://pyscript.net/releases/2024.4.2/core.js"></script>
199+
<link rel="stylesheet" href="https://pyscript.net/releases/2024.5.1/core.css">
200+
<script type="module" src="https://pyscript.net/releases/2024.5.1/core.js"></script>
193201
</head>
194202
<body>
195203
<script type="mpy">

docs/user-guide/workers.md

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,22 @@ attribute flag:
5454
<script type="py" src="./my-worker-code.py" worker></script>
5555
```
5656

57-
Code running in the worker needs to be able to access the web page running in
58-
the main thread. This is achieved via [builtin helper utilities](../builtins).
57+
Alternatively, to launch a worker from within Python running on the main thread
58+
use the [pyscript.PyWorker](../builtins/#pyscriptpyworker) class and must
59+
reference both the target Python script and interpreter type:
60+
61+
```python title="Launch a worker from within Python"
62+
from pyscript import PyWorker
63+
64+
# The type MUST be given and can be either `micropython` or `pyodide`
65+
PyWorker("my-worker-code.py", type="micropython")
66+
```
67+
68+
## Worker interactions
69+
70+
Code running in the worker needs to be able to interact with code running in
71+
the main thread and perhaps have access to the web page. This is achieved via
72+
some helpful [builtin utilities](../builtins).
5973

6074
!!! note
6175

@@ -67,3 +81,53 @@ the main thread. This is achieved via [builtin helper utilities](../builtins).
6781
[consult the XWorker](https://pyscript.github.io/polyscript/#xworker)
6882
related documentation from the PolyScript project for how to make use of
6983
these features.
84+
85+
To synchronise serializable data between the worker and the main thread use
86+
[the `sync` function](../builtins/#pyscriptsync) in the worker to reference a
87+
function registered on the main thread:
88+
89+
```python title="Python code running on the main thread."
90+
from pyscript import PyWorker
91+
92+
def hello(name="world"):
93+
return(f"Hello, {name}")
94+
95+
# Create the worker.
96+
worker = PyWorker("./worker.py", type="micropython")
97+
98+
# Register the hello function as callable from the worker.
99+
worker.sync.hello = hello
100+
```
101+
102+
```python title="Python code in the resulting worker."
103+
from pyscript import sync, window
104+
105+
greeting = sync.hello("PyScript")
106+
window.console.log(greeting)
107+
```
108+
109+
The values passed between the main thread and the worker **must be
110+
serializable**. Try the example given above via
111+
[this project on PyScript.com](https://pyscript.com/@ntoll/tiny-silence/latest).
112+
113+
No matter if your code is running on the main thread or in a web worker,
114+
both the [`pyscript.window`](../builtins/#pyscriptwindow) (representing the main
115+
thread's global window context) and
116+
[`pyscript.document`](../builtins/#pyscriptdocument) (representing the web
117+
page's
118+
[document object](https://developer.mozilla.org/en-US/docs/Web/API/Document))
119+
will be available and work in the same way. As a result, a worker can reach
120+
into the DOM and access some `window` based APIs.
121+
122+
!!! warning
123+
124+
Access to the `window` and `document` objects is a powerful feature. Please
125+
remember that:
126+
127+
* Arguments to and the results from such calls, when used in a worker,
128+
**must be serializable**, otherwise they won't work.
129+
* If you manipulate the DOM via the `document` object, and other workers or
130+
code on the main thread does so too, **they may interfere with each other
131+
and produce unforeseen problematic results**. Remember, with great power
132+
comes great responsibility... and we've given you a bazooka (so please
133+
remember not to shoot yourself in the foot with it).

0 commit comments

Comments
 (0)