- Notifications
You must be signed in to change notification settings - Fork11
MicroPyScript: A test harness for multiple runtimes in PyScript
License
pyscript/MicroPyScript
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
A small, simple kernel of PyScript, made for testing purposes in the spirit ofacode spike.
This is the way:
- Obvious code.
- Simple is good.
- No dependencies.
- Vanilla JavaScript.
- Pluggable.
- Comments.
- (Some) tests.
- Build for change.
This is a foundation for lightweight testing of Python interpreters thattarget WASM. Inspired by code in the "real" PyScript website and our plans forplugins and simple event based coordination.
Complexity, edge cases and customization is (hopefully) confined to plugins andbespoke interpreters.
That is all.
In order to compile MicroPython you'll need to ensure you have the expecteddev tools described here:
https://docs.micropython.org/en/latest/develop/gettingstarted.html
Otherwise, common tasks are scripted by a Makefile (tested on Linux):
$ makeThere's no default Makefile target right now. Try:make setup - clone the required repositories.make update - update the emsdk compiler.make mp - compile MicroPython for WASM into the mpbuild directory.make serve - serve the project at: http://0.0.0.0:8000/make test - while serving the app, run the test suite in browser.
To get a working development environment with MicroPython run:
$ make setup$ make update$ make mp
To check things are working:
$ make serve
Then point your browser tohttp://0.0.0.0:8000/ to see the first page of aninteractive technical report about using MicroPython. You should be able tochange the interpreter frommicropython
topyodide
and things should justwork as before, but with a different interpreter at the bottom of the PyScriptstack.
TESTS ARE CURRENTLY BROKEN
For the sake of simplicity (and familiarity) we use theJasmine test framework to exercise theJavaScript aspects of our code.
Ensure the project is being served (make serve
) and in a different shell, inthe root of this project, typemake test
. Your default browser should openand run the Jasmine based test suite.
The PyScript core only loads configuration, starts the Python interpreter,allows the registration of plugins and adds files to the interpreter'sfilesystem. All other logic, capabilities and features are contained in theplugins.
Currently, only two plugins are provided:
- One built into PyScript that implements the core
<py-script>
tag. - The other (in
customtags.js
) implements the<py-repl>
tag to demonstratea "third party" plugin.
The story of PyScript's execution is roughly as follows:
- Configuration is loaded from the
<py-config>
tag. Once complete thepy-configured
event is dispatched, containing theconfig
object basedupon default values overridden by the content of the<py-config>
tag. - When the
py-configured
event is dispatched three things happen:- The interpreter is loaded via injecting a
<script>
tag that references theinterpreter's URL. Once loaded thepy-interpreter-loaded
event is dispatched. - Plugins are registered and have their
configure
function called. For eachplugin registered apy-plugin-registered
event is dispatched, containingthe (potentially changed)config
, and a reference to the newly registeredplugin. - The content of the files to be added to the interpreter's filesystem arefetched. Once downloaded each file causes a
py-file-fetched
event to bedispatched with the path and content of the file attached to it.
- The interpreter is loaded via injecting a
- When
py-interpreter-loaded
is dispatched two things happen:- The interpreter is instantiated / started. Once complete the
py-interpreter-ready
event is dispatched. - All registered plugins have their
start
function called and apy-plugin-started
event is dispatched for each plugin.
- The interpreter is instantiated / started. Once complete the
- When the
py-interpreter-ready
event is dispatched all plugins have theironInterpreterReady
function called with theconfig
andinterpreter
objects. At this point all files are copied onto the interpreter'sfilesystem. When all the files are copied thepy-files-loaded
event isdispatched. - When both the interpreter and filesystem are finished setting up and in aready state, the
py-finished-setup
event is dispatched to signal PyScriptis ready to evaluate user's code. - Any plugins registered after the interpreter is ready immediately have their
configure
,start
andonInterpreterReady
functions called, with thepy-plugin-registered
andpy-plugin-started
events being dispatched.
That's it!
Whenpyscript.js
is run, it creates awindow.PyScript
object that containsread-only references to theconfig
, registeredplugins
,availableInterpreters
, theinterpreter
used on the page, anisInterpreterReady
flag, aregisterPlugin
function (see below) and arunPython(code)
function that takes a string of Python.
There are copious comments in thepyscript.js
file. My intention is forsimplicity, lack of onerous dependencies (bye-byenpm
), andunderstandability. This code is good if it's easy to understand what's goingon. To this end, it's laid out in a "literate" manner, so the code "tells thestory" of this implementation of PyScript by reading it from top to bottom.
Plugins are inspired by Antonio's suggestionfound here,and should be relatively self explanatory.
Since simplicity is the focus, plugins are simply JavaScript objects.
Such objects are expected they have aname
attribute referencing a stringnaming the plugin (useful for logging purposes). Plugins should also provideone or more of the following functions attached to them, called in thefollowing order (as the lifecycle of the plugin):
configure(config)
- Gives the plugin early access to theconfig
object.Potentially, the plugin can modify it, and modifications will be visible tolater steps and other plugins. Plugins must only modify the config at thispoint in their life-cycle. Examples of things which plugins might want to doat this point:- Early sanity check about their own options.
- Rename/remap some options.
- Add new packages to install.
- Modify options for other plugins (e.g. a debugger plugin might set theoption
show_terminal
).
start(config)
- The main entry point for plugins. At this point, configshould not be modified by the plugin. Example use cases:- Define custom HTML elements.
- Start fetching external resources.
onInterpreterReady(config, interpreter)
- Called once the interpreter isready to evaluate Python code. Example use cases:pip install
packages.- Import/initialize Python plugins.
The following events, dispatched by PyScript itself, are related to plugins:
py-plugin-registered
- Dispatched when a plugin is registered (and theevent contains a reference to the newly registered plugin). This happensimmediately after the plugin'sconfigure
function is called.py-plugin-started
- Dispatched immediately after a plugin'sstart
function is called. The event contains a reference to the started plugin.py-interpreter-ready
- causes each plugin'sonInterpreterReady
functionto be called.
If a plugin is registeredafter the interpreter is ready, all three functionsare immediately called in the expected sequence, one after the other.
The recommended way to create and register plugins is:
constmyPlugin=function(e){/* Private and internal logic, event handlers and event dispatch can happen within the closure defined by this function. e.g. const FOO = "bar"; function foo() { const myEvent = new CustomEvent("my-event", {detail: {"foo": FOO}}); document.dispatchEvent(myEvent); } function onFoo(e) { console.log(e.detail); } document.addEventListener("my-event", onFoo); ... */constplugin={name:"my-plugin",configure:function(config){// ...},start:function(config){// ...foo();},onInterpreterReady:function(config,interpreter){// ...}};window.pyScript.registerPlugin(plugin);}();document.addEventListener("py-configured",myPlugin);
Then in your HTML file:
<scriptsrc="myplugin.js"></script><scriptsrc="pyscript.js"type="module"></script>
TheInterpreter
class abstracts away all the implementation details of thevarious Python interpreters we might use.
To see a complete implementation see theMicroPythonInterpreter
class thatinherits fromInterpreter
. There is also an incompletePyodideInterpreter
class so I was able to compare and contrast the differences betweenimplementations and arrive at a general abstraction (still very much a work inprogress). Comments in the code should explain what's going on in terms of thelife-cycle and capabilities of a "interpreter".
The afore mentionedMicroPythonInterpreter
,CPythonInterpreter
andPyodideInterpreter
all, to a greater or lesser extent, define a uniform shimaround their respective interpreter. The MicroPython one is most complete, butstill needs work as I make changes to how MicroPython itself exposesstdout
,stderr
and consumesstdin
.
Who knows..? But this is a good scaffold for testing different Pythoninterpreters.
Next steps:
- More comprehensive tests.
CPythonInterpreter
fully implemented.PyodideInterpreter
finished.MicroPythonInterpreter
refactored after making MicroPython play nicer withstdout
andstderr
.- A uniform way to
pip install
packages in each interpreter. - A uniform JavaScript gateway from within each interpreter.
- A uniform
navigator
object through which to access the DOM from within eachinterpreter. - Running in web-workers (and associated message passing work), for eachinterpreter.
That's it..! ;-)
About
MicroPyScript: A test harness for multiple runtimes in PyScript
Resources
License
Uh oh!
There was an error while loading.Please reload this page.