From 50a92ee62fc706fe510420cfe778b852715f170b Mon Sep 17 00:00:00 2001 From: webreflection Date: Mon, 3 Jun 2024 16:30:41 +0200 Subject: [PATCH 1/7] Updates around latest changes and docs --- README.md | 1 + docs/faq.md | 11 ++++-- docs/user-guide/builtins.md | 21 ++++++++++ docs/user-guide/editor.md | 47 ++++++++++++++++++++++ docs/user-guide/terminal.md | 20 ++++++++++ docs/user-guide/workers.md | 79 +++++++++++++++++++++++++++++++++++++ 6 files changed, 176 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5824a7f..c0f8517 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ of this repository). # example of a simple virtual environment # creation from the root of this project python -m venv . +./bin/pip install --upgrade setuptools ./bin/pip install -r requirements.txt ``` diff --git a/docs/faq.md b/docs/faq.md index 5b75b94..6de69e4 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -705,7 +705,7 @@ done"` message is written to the browser's console. Applications need third party packages and [PyScript can be configured to automatically install packages for you](user-guide/configuration/#packages). -Yet [packaging can be a complicated beast](#python-packages), so here are some +Yet packaging can be a complicated beast, so here are some hints for a painless packaging experience with PyScript. There are essentially four ways in which a third party package can become @@ -724,9 +724,10 @@ available in PyScript. 3. Reference hosted Python source files, to be included on the file system, via the [`files` setting](../user-guide/configuration/#files). 4. Create a folder containing the package's files and sub folders, and create - a hosted `.zip` or `.tgz`/`.tar.gz` archive to be decompressed into the file + a hosted `.zip` or `.tgz`/`.tar.gz`/`.whl` archive to be decompressed into the file system (again, via the [`files` setting](../user-guide/configuration/#files)). +5. provide your own `.whl` package as part of the `packages = [...]` list to see it available within your project #### Host a package @@ -768,7 +769,7 @@ packages onto the Python path: ``` -#### Code archive (`zip`/`tgz`) +#### Code archive (`zip`/`tgz`/`whl`) Compress all the code you want into an archive (using either either `zip` or `tgz`/`tar.gz`). Host the resulting archive and use the @@ -1207,6 +1208,8 @@ js.callback( ) ``` +Ultimately though, Pyodide maps can be consumed out of the box as literals, so that *some* required conversion might not be needed anymore and `to_js` might be enough, without specifying the `dict_converter` as that's inferred, once consumed, behind the scene. + In addition, MicroPython's version of `to_js` takes the opposite approach (for many of the reasons stated above) and converts Python dictionaries to object literals instead of `Map` objects. @@ -1215,6 +1218,8 @@ As a result, **the PyScript `pyscript.ffi.to_js` ALWAYS returns a JavaScript object literal by default when converting a Python dictionary** no matter if you're using Pyodide or MicroPython as your interpreter. +That being said, in MicroPython things work closely to JS users' expectations, memory friendly too, so using the `to_js` is more a cross-interpreter guard than a necessity these days. + #### Caveat !!! warning diff --git a/docs/user-guide/builtins.md b/docs/user-guide/builtins.md index b3de20e..05b6844 100644 --- a/docs/user-guide/builtins.md +++ b/docs/user-guide/builtins.md @@ -571,6 +571,27 @@ from pyscript import sync sync.hello("PyScript") ``` +### `pyscript.py_modules` + +Bootstrapping *Pyodide* or *MicroPython* with a lot of packages might degrade the time to readyness. + +When some dependency is needed only under certain conditions or after some specific user action, we are offering an asynchronuos way to lazily import packages that were not present already in the *config*. + +A bare minimal example of how this feature works can be summarized as such: + +```html title="pyscript.py_modules example" + +``` + +The `py_modules` then returns an asynchronous tuple with one or more modules passed as string and it's compatible with `.whl` packages too. + + ## HTML attributes As a convenience, and to ensure backwards compatibility, PyScript allows the diff --git a/docs/user-guide/editor.md b/docs/user-guide/editor.md index add0cef..b00ba5a 100644 --- a/docs/user-guide/editor.md +++ b/docs/user-guide/editor.md @@ -16,6 +16,11 @@ or `mpy-editor` (for MicroPython), the plugin creates a visual code editor, with code highlighting and a "run" button to execute the editable code contained therein in a non-blocking worker. +!!! info + + Once clicked, the *Run* button will show a spinner until the code is executed. This might not be visible if the code took nothing to execute, but if the code took any measurable time longer, one will notice such spinner before results will be shown. + + The interpreter is not loaded onto the page until the run button is clicked. By default each editor has its own independent instance of the specified interpreter: @@ -61,6 +66,8 @@ The outcome of these code fragments should look something like this: Hovering over the Python editor reveals the "run" button. +### Setup + Sometimes you need to create a pre-baked Pythonic context for a shared environment used by an editor. This need is especially helpful in educational situations where boilerplate code can be run, with just the important salient @@ -128,6 +135,46 @@ not expect the same behavior regular *PyScript* elements follow, most notably: * There is no special reference to the underlying editor instance, while there is both `script.terminal` or `__terminal__` in the terminal. +## Read / Write / Execute + +Behind the scene, we bootstrap an editor that provides: + + * highlights around the Python code in it + * a Run button to execute the code + * a `target` reference where the code output lands, once printed + +This is all great and sound, but there is also a way to read the *editor* code, and update it with ease, that's the `code` accessor any editor gets, once bootstrapped: + +```python +from pyscript import document + +# grab the editor script reference +editor = document.querySelector('#editor') + +# output its content +print(editor.code) + +# or update its content +editor.code = """ +a = 1 +b = 2 +print(a + b) +""" +``` + +To execute that new editor content a user might click the *Run* button one more time, or the driver of such editor can use `editor.process(editor.code)`, or any other arbitrary code, to actually bypass the need to click *Run* and execute the code passed along the `.process(...)` invoke. + +These utilities are helpful to let consumers of the editor change its view state and/or execute it out the box. + +## Config + +Differently from ` + PyWorker - mpy bootstrapping pyodide example + + + + +``` + +**main.py** +```Python title="MicroPython bootstrapping a Pyodide worker" +from pyscript import PyWorker, document + +# bootstrap the pyodide worker with optional config too +# the worker here is: +# * owned by this script, no JS or Pyodide code in the same page can access it +# * it allows pre-sync methods exposure +# * it exposes a ready Promise to await pyodide on the worker side +# * it then allows using post-sync (utilities exposed by pyodide) +worker = PyWorker("worker.py", type="pyodide") + +# expose an utility that can be invoke *out of the box* in worker.py +worker.sync.greetings = lambda: print("Pyodide bootstrapped") + +print("before ready") +# await for Pyodide to complete its bootstrap +await worker.ready +print("after ready") + +# await any exposed utility exposed via Pyodide +result = await worker.sync.heavy_computation() +print(result) + +# show the result at the end of the body +document.body.append(result) + +# here we free memory and get rid of everything +worker.terminate() +``` + +**worker.py** +```Python title="A Pyodide worker" +from pyscript import sync + +# use any already exposed utility from main.py +sync.greetings() + +# expose any method meant to be used from main +sync.heavy_computation = lambda: 6 * 7 +``` + +Save these files in a *tmp* folder and use `npx mini-coi ./tmp` to reach out that `index.html` and see the following outcome in *devtools*: + +``` +before ready +Pyodide bootstrapped +after ready +42 +``` From 441c89d28c5bec7945bea132f876c34ed56ecd91 Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Tue, 4 Jun 2024 12:17:44 +0100 Subject: [PATCH 2/7] Edits. --- docs/faq.md | 23 +++++++++------- docs/user-guide/builtins.md | 19 ++++++++----- docs/user-guide/editor.md | 40 +++++++++++++--------------- docs/user-guide/terminal.md | 50 ++++++++++++++++++++++------------ docs/user-guide/workers.md | 53 ++++++++++++++++++++----------------- 5 files changed, 107 insertions(+), 78 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 6de69e4..3987799 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -705,10 +705,10 @@ done"` message is written to the browser's console. Applications need third party packages and [PyScript can be configured to automatically install packages for you](user-guide/configuration/#packages). -Yet packaging can be a complicated beast, so here are some +Yet [packaging can be a complicated beast](#python-packages), so here are some hints for a painless packaging experience with PyScript. -There are essentially four ways in which a third party package can become +There are essentially five ways in which a third party package can become available in PyScript. 1. The module is already part of either the Pyodide or MicroPython @@ -724,10 +724,11 @@ available in PyScript. 3. Reference hosted Python source files, to be included on the file system, via the [`files` setting](../user-guide/configuration/#files). 4. Create a folder containing the package's files and sub folders, and create - a hosted `.zip` or `.tgz`/`.tar.gz`/`.whl` archive to be decompressed into the file - system (again, via the + a hosted `.zip` or `.tgz`/`.tar.gz`/`.whl` archive to be decompressed into + the file system (again, via the [`files` setting](../user-guide/configuration/#files)). -5. provide your own `.whl` package as part of the `packages = [...]` list to see it available within your project +5. Provide your own `.whl` package and reference it via a URL in the + `packages = [...]` list. #### Host a package @@ -1207,10 +1208,16 @@ js.callback( ) ) ``` +!!! info -Ultimately though, Pyodide maps can be consumed out of the box as literals, so that *some* required conversion might not be needed anymore and `to_js` might be enough, without specifying the `dict_converter` as that's inferred, once consumed, behind the scene. + Thanks to a + [recent change in Pyodide](https://github.com/pyodide/pyodide/pull/4576), + such `Map` instances are + [duck-typed](https://en.wikipedia.org/wiki/Duck_typing) to behave like + object literals. Conversion may not be needed anymore, and `to_js` may just + work without the need of the `dict_converter`. Please check. -In addition, MicroPython's version of `to_js` takes the opposite approach (for +MicroPython's version of `to_js` takes the opposite approach (for many of the reasons stated above) and converts Python dictionaries to object literals instead of `Map` objects. @@ -1218,8 +1225,6 @@ As a result, **the PyScript `pyscript.ffi.to_js` ALWAYS returns a JavaScript object literal by default when converting a Python dictionary** no matter if you're using Pyodide or MicroPython as your interpreter. -That being said, in MicroPython things work closely to JS users' expectations, memory friendly too, so using the `to_js` is more a cross-interpreter guard than a necessity these days. - #### Caveat !!! warning diff --git a/docs/user-guide/builtins.md b/docs/user-guide/builtins.md index 05b6844..a99ef38 100644 --- a/docs/user-guide/builtins.md +++ b/docs/user-guide/builtins.md @@ -573,13 +573,20 @@ sync.hello("PyScript") ### `pyscript.py_modules` -Bootstrapping *Pyodide* or *MicroPython* with a lot of packages might degrade the time to readyness. +!!! warning + + **This is an experimental feature.** -When some dependency is needed only under certain conditions or after some specific user action, we are offering an asynchronuos way to lazily import packages that were not present already in the *config*. + Feedback and bug reports are welcome! -A bare minimal example of how this feature works can be summarized as such: +If you have a lot of packages referenced in your configuration, startup +performance may be degraded as these are downloaded. -```html title="pyscript.py_modules example" +If a package is only needed under certain circumstances, we provide an +asynchronous way to import packages that were not originally referenced in your +configuration. + +```html title="a pyscript.py_modules example" ``` -The `py_modules` then returns an asynchronous tuple with one or more modules passed as string and it's compatible with `.whl` packages too. - +The `py_modules` call returns an asynchronous tuple containing the modules +referenced as string arguments. ## HTML attributes diff --git a/docs/user-guide/editor.md b/docs/user-guide/editor.md index b00ba5a..1765d55 100644 --- a/docs/user-guide/editor.md +++ b/docs/user-guide/editor.md @@ -18,7 +18,8 @@ contained therein in a non-blocking worker. !!! info - Once clicked, the *Run* button will show a spinner until the code is executed. This might not be visible if the code took nothing to execute, but if the code took any measurable time longer, one will notice such spinner before results will be shown. + Once clicked, the "run" button will show a spinner until the code is + executed. This may not be visible if the code evaluation completed quickly. The interpreter is not loaded onto the page until the run button is clicked. By @@ -137,43 +138,40 @@ not expect the same behavior regular *PyScript* elements follow, most notably: ## Read / Write / Execute -Behind the scene, we bootstrap an editor that provides: - - * highlights around the Python code in it - * a Run button to execute the code - * a `target` reference where the code output lands, once printed - -This is all great and sound, but there is also a way to read the *editor* code, and update it with ease, that's the `code` accessor any editor gets, once bootstrapped: +Sometimes you need to programatically read, write or execute code in an +editor. Once PyScript has started, every py-editor/mpy-editor script tag gets +a `code` accessor attached to it. ```python from pyscript import document -# grab the editor script reference +# Grab the editor script reference. editor = document.querySelector('#editor') -# output its content +# Output the live content of the editor. print(editor.code) -# or update its content +# Update the live content of the editor. editor.code = """ a = 1 b = 2 print(a + b) """ -``` - -To execute that new editor content a user might click the *Run* button one more time, or the driver of such editor can use `editor.process(editor.code)`, or any other arbitrary code, to actually bypass the need to click *Run* and execute the code passed along the `.process(...)` invoke. -These utilities are helpful to let consumers of the editor change its view state and/or execute it out the box. - -## Config - -Differently from ` + + @@ -163,8 +163,8 @@ In the end, our HTML should look like this: 🦜 Polyglot - Piratical PyScript - - + +

Polyglot 🦜 💬 🇬🇧 ➡️ 🏴‍☠️

diff --git a/docs/user-guide/first-steps.md b/docs/user-guide/first-steps.md index 601b274..8690d96 100644 --- a/docs/user-guide/first-steps.md +++ b/docs/user-guide/first-steps.md @@ -20,9 +20,9 @@ CSS: - + - + diff --git a/docs/user-guide/plugins.md b/docs/user-guide/plugins.md index 84ee985..fbf256d 100644 --- a/docs/user-guide/plugins.md +++ b/docs/user-guide/plugins.md @@ -99,7 +99,7 @@ For example, this will work because all references are contained within the registered function: ```js -import { hooks } from "https://pyscript.net/releases/2024.5.2/core.js"; +import { hooks } from "https://pyscript.net/releases/2024.6.1/core.js"; hooks.worker.onReady.add(() => { // NOT suggested, just an example! @@ -113,7 +113,7 @@ hooks.worker.onReady.add(() => { However, due to the outer reference to the variable `i`, this will fail: ```js -import { hooks } from "https://pyscript.net/releases/2024.5.2/core.js"; +import { hooks } from "https://pyscript.net/releases/2024.6.1/core.js"; // NO NO NO NO NO! ☠️ let i = 0; @@ -146,7 +146,7 @@ the page. ```js title="log.js - a plugin that simply logs to the console." // import the hooks from PyScript first... -import { hooks } from "https://pyscript.net/releases/2024.5.2/core.js"; +import { hooks } from "https://pyscript.net/releases/2024.6.1/core.js"; // The `hooks.main` attribute defines plugins that run on the main thread. hooks.main.onReady.add((wrap, element) => { @@ -196,8 +196,8 @@ hooks.worker.onAfterRun.add(() => { - - + + + PyWorker - mpy bootstrapping pyodide example diff --git a/version.json b/version.json index d3d8baf..b73d389 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "2024.5.2" + "version": "2024.6.1" } From a17d2dba2b333b7b0511a1aced37fd37fa727ce8 Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Tue, 4 Jun 2024 12:25:22 +0100 Subject: [PATCH 4/7] Re-add MicroPython clarification. --- docs/faq.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 3987799..cb43451 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1223,7 +1223,10 @@ literals instead of `Map` objects. As a result, **the PyScript `pyscript.ffi.to_js` ALWAYS returns a JavaScript object literal by default when converting a Python dictionary** no matter if -you're using Pyodide or MicroPython as your interpreter. +you're using Pyodide or MicroPython as your interpreter. Furthermore, when +using MicroPython, because things are closer to idiomatic JavaScript behaviour, +you may not even need to use `to_js` unless you want to ensure +cross-interpreter compatibility. #### Caveat From 0e37952427e5dbcd6fe74a19862da6e4d28b2cdb Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Tue, 4 Jun 2024 12:28:57 +0100 Subject: [PATCH 5/7] Clarification about mini-coi. --- docs/user-guide/workers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/workers.md b/docs/user-guide/workers.md index abb8349..061de84 100644 --- a/docs/user-guide/workers.md +++ b/docs/user-guide/workers.md @@ -219,7 +219,7 @@ sync.heavy_computation = lambda: 6 * 7 ``` Save these files in a `tmp` folder, ensure [your headers](#http-headers) (just -use `npx mini-coi ./tmp` and update `index.html`) then see the following +use `npx mini-coi ./tmp` and serve via localhost) then see the following outcome in the browser's devtools. ``` From ad8e0988f18de6f877df07a972e94b08f8a7ed48 Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Tue, 4 Jun 2024 18:03:18 +0100 Subject: [PATCH 6/7] py_import and js_import. --- docs/user-guide/builtins.md | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/docs/user-guide/builtins.md b/docs/user-guide/builtins.md index a99ef38..4d582d7 100644 --- a/docs/user-guide/builtins.md +++ b/docs/user-guide/builtins.md @@ -571,7 +571,7 @@ from pyscript import sync sync.hello("PyScript") ``` -### `pyscript.py_modules` +### `pyscript.py_import` !!! warning @@ -579,25 +579,46 @@ sync.hello("PyScript") Feedback and bug reports are welcome! -If you have a lot of packages referenced in your configuration, startup +If you have a lot of Python packages referenced in your configuration, startup performance may be degraded as these are downloaded. -If a package is only needed under certain circumstances, we provide an +If a Python package is only needed under certain circumstances, we provide an asynchronous way to import packages that were not originally referenced in your configuration. -```html title="a pyscript.py_modules example" +```html title="A pyscript.py_import example." ``` -The `py_modules` call returns an asynchronous tuple containing the modules -referenced as string arguments. +The `py_import` call returns an asynchronous tuple containing the Python +modules provided by the packages referenced as string arguments. + +### `pyscript.js_import` + +If you have a lot of JavaScript modules referenced in your configuration, +startup performance may be degraded as these are downloaded. + +If a JavaScript module is only needed under certain circumstances, we provide +an asynchronous way to import packages that were not originally referenced in +your configuration. + +```html title="A pyscript.js_import example." +