Skip to content

Architecture, Lifecycle & Interpreters

Core concepts

PyScript's architecture has two core concepts:

  1. A small, efficient and powerful kernel called PolyScript is the foundation upon which PyScript and plugins are built.
  2. The PyScript stack inside the browser is simple and clearly defined.

PolyScript

PolyScript is the core of PyScript.

Danger

Unless you are an advanced user, you only need to know that PolyScript exists, and it can be safely ignored.

PolyScript's purpose is to bootstrap the platform and provide all the necessary core capabilities. Setting aside PyScript for a moment, to use just PolyScript requires a <script> reference to it, along with a further <script> tag defining how to run your code.

Bootstrapping with PolyScript
<!doctype html>
<html>
    <head>
        <!-- this is a way to automatically bootstrap polyscript -->
        <script type="module" src="https://cdn.jsdelivr.net/npm/polyscript"></script>
    </head>
    <body>
        <!--
            Run some Python code with the MicroPython interpreter, but without
            the extra benefits provided by PyScript.
        -->
        <script type="micropython">
            from js import document
            document.body.textContent = 'polyscript'
        </script>
    </body>
</html>

Warning

PolyScript is not PyScript.

PyScript enhances the available Python interpreters with convenient features, helper functions and easy-to-use yet powerful capabilities.

These enhancements are missing from PolyScript.

PolyScript's capabilities, upon which PyScript is built, can be summarised as:

  • Evaluation of code via <script> tags.
  • Handling browser events via code evaluated with an interpreter supported by PolyScript.
  • A clear way to use workers via the XWorker class and its related reference, xworker.
  • Custom scripts to enrich PolyScript's capabilities.
  • A ready event dispatched when an interpreter is ready and about to run code, and a done event when an interpreter has finished evaluating code.
  • Hooks, called at clearly defined moments in the page lifecycle, provide a means of calling user defined functions to modify and enhance PolyScript's default behaviour.
  • Multiple interpreters (in addition to Pyodide and MicroPython, PolyScript works with Lua and Ruby - although these are beyond the scope of this project).

How to contribute

The Polyscript scope can then be summarized as such:

  • provide as little as possible abstraction to bootstrap different interpreters (Pyodide, MicroPython, R, Lua, Ruby, others ...) without affecting PyScript goal
  • simplify the bootstrap of any interpreter through DOM primitives (script, custom-elements, or both ...)
  • understand and parse any explicit configuration option (being this a file to parse or an already parsed object literal)
  • forward any defined hook to the interpreter, either main or worker thread, so that code before, or right after, can be transparently executed
  • orchestrate a single bootstrap per each involved element, being this a script or a <custom-script> on the living page
  • ensure a Worker, optionally Atomics and SharedArrayBuffer based, stand-alone environment can be bootstrapped and available for at least not experimental runtime (Lua, Ruby, others)

While this is a simplification of all the things polyscript does behind the scene, the rule of thumb to "blame" polyscript for anything affecting your project/idea is likely:

  • is my interpreter not loading?
  • where are errors around my interpreter not loading?
  • is my HTML event not triggering? (py-* or mpy-* or ...)
  • how come this feature handled explicitly by polyscript is not reflected in my PyScript project? (this is likely and advanced issue/use case, but it's always OK to ask why in polyscript, and answers will flow accordingly)

To summarize, as much as PyScript users should never encounter one of these issues, it is possible some specific feature request or issue might be enabled in polyscript first to land then in PyScript.

Coincident

At the core of polyscript project there is one extra project enabling all the seamless worker to main, and vice-versa, features called coincident.

The purpose of this project is to enable, in a memory / garbage collector friendly way, a communication channel between one thread and another, handling the main thread dealing with workers references, or the other way around, as its best core feature.

Anything strictly related to SharedArrayBuffer issues is then an orchestration coincident is handling, and to some extend also anything memory leak related could as well fall down to this module purpose and scope.

In a nutshell, this project takes care of, and is responsible for, the following patterns:

  • invoking something from a worker that refers the main thread somehow fails
  • there is a reproducible and cross platform (browsers) memory leak to tackle
  • some function invoke with some specific argument from a worker doesn't produce the expected result

All these scenarios are unlikely to happen with PyScript project, as these are all battle tested and covered with such general purpose cross-env/realm oriented project way before landing in PyScript, but if you feel something is really off, leaking, or badly broken, please feel free to file an issue in this project and, once again, there is never a silly question about it so that, as long as you can provide any minimal reproducible issue, all questions and issues are more than welcome!

The stack

The stack describes how the different building blocks inside a PyScript application relate to each other:

  • Everything happens inside the context of the browser (represented by the black border). The browser tab for your PyScript app is your sandboxed computing environment.

Failure

PyScript is simply Python running in the browser. It is an unfamiliar concept that some fail to remember.

  • PyScript isn't running on a server hosted in the cloud.
  • PyScript doesn't use the version of Python running natively on the user's operating system.
  • PyScript only runs IN THE BROWSER (and nowhere else).
  • At the bottom of the stack are the Python interpreters compiled to WASM. They evaluate your code and interact with the browser via the FFI.
  • The PyScript layer makes it easy to use and configure the Python interpreters. There are two parts to this:
    1. The PolyScript kernel (see above), that bootstraps everything and provides the core capabilities.
    2. PyScript and related plugins that sit atop PolyScript to give us an easy-to-use Python platform in the browser.
  • Above the PyScript layer are either:
    1. Application frameworks, modules and libraries written in Python that you use to create useful applications.
    2. Your code (that's your responsibility).

Lifecycle

If the architecture explains how components relate to each other, the lifecycle explains how things unfold. It's important to understand both: it will help you think about your own code and how it sits within PyScript.

Here's how PyScript unfolds through time:

  • The browser is directed to a URL. The response is HTML.
  • When parsing the HTML response the browser encounters the <script> tag that references PyScript. PyScript is loaded and evaluated as a JavaScript module, meaning it doesn't hold up the loading of the page and is only evaluated when the HTML is fully parsed.
  • The PyScript module does broadly six things:
    1. Discover Python code referenced in the page.
    2. Evaluate any configuration on the page (either via single <py-config> or <mpy-config> tags or the config attribute of a <script>, <py-script> or <mpy-script> tag).
    3. Given the detected configuration, download the required interpreter.
    4. Setup the interpreter's environment. This includes any plugins, packages, files or JavaScript modulesthat need to be loaded.
    5. Make available various builtin helper objects and functions to the interpreter's environment (accessed via the pyscript module).
    6. Only then use the interpreter in the correctly configured environment to evaluate the detected Python code.
  • When an interpreter is ready the py:ready or mpy:ready events are dispatched, depending which interpreter you've specified (Pyodide or MicroPython respectively).
  • Finally, a py:done event is dispatched after every single script referenced from the page has finished.

In addition, various "hooks" are called at different moments in the lifecycle of PyScript. These can be used by plugin authors to modify or enhance the behaviour of PyScript. The hooks, and how to use them, are explored further in the section on plugins.

Warning

A web page's workers have completely separate environments to the main thread.

It means configuration in the main thread can be different to that for an interpreter running on a worker. In fact, you can use different interpreters and configuration in each context (for instance, MicroPython on the main thread, and Pyodide on a worker).

Think of workers as completely separate sub-processes inside your browser tab.

Interpreters

Python is an interpreted language, and thus needs an interpreter to work.

PyScript supports two versions of the Python interpreter that have been compiled to WASM: Pyodide and MicroPython. You should select which one to use depending on your use case and acceptable trade-offs.

Info

In future, and subject to progress, we hope to make available a third Pythonic option: SPy, a staticially typed version of Python compiled directly to WASM.

Both interpreters make use of emscripten, a compiler toolchain (using LLVM), for emitting WASM assets for the browser. Emscripten also provides APIs so operating-system level features such as a sandboxed file system (not the user's local machine's filesystem), IO (stdin, stdout, stderr etc,) and networking are available within the context of a browser.

Both Pyodide and MicroPython implement the same robust PythonJavaScript foreign function interface (FFI). This bridges the gap between the browser and Python worlds.

Pyodide

Pyodide is a version of the standard CPython interpreter, patched to compile to WASM and work in the browser.

It includes many useful features:

  • The installation of pure Python packages from PyPI via the micropip package installer.
  • An active, friendly and technically outstanding team of volunteer contributors (some of whom have been supported by the PyScript project).
  • Extensive official documentation, and many tutorials found online.
  • Builds of Pyodide that include popular packages for data science like Numpy, Scipy and Pandas.

Warning

You may encounter an error message from micropip that explains it can't find a "pure Python wheel" for a package. Pyodide's documentation explains what to do in this situation.

Briefly, some packages with C extensions have versions compiled for WASM and these can be installed with micropip. Packages containing C extensions that are not compiled for WASM cause the "pure Python wheel" error.

There are plans afoot to make WASM a target in PyPI so packages with C extensions are automatically compiled to WASM.

MicroPython

MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and in constrained environments (like the browser).

Everything needed to view a web page in a browser needs to be delivered over the network. The smaller the asset to be delivered can be, the better. MicroPython, when compressed for delivery to the browser, is only around 170k in size - smaller than many images found on the web.

This makes MicroPython particularly suited to browsers running in a more constrained environment such as on a mobile or tablet based device. Browsing with these devices often uses (slower) mobile internet connections. Furthermore, because MicroPython is lean and efficient it still performs exceptionally well on these relatively underpowered devices.

Thanks to collaboration between the MicroPython and PyScript projects, there is a foreign function interface for MicroPython. The MicroPython FFI deliberately copies the API of the FFI originally written for Pyodide - meaning it is relatively easy to migrate between the two supported interpreters.