Code execution#

The exec directive allows executing code from the browser.

Directive#

.. exec:: runner [env]#

This directive is a code-block that allows executing code in the browser. It supports most of the options of code-block, and a few more described below.

runner is one of the supported code runners (html, micropython, python, sql). env is an optional environment name. Code executed in distinct environments is isolated from each other. The default environment name is the empty string.

Options

:after: name [name...]#

Execute one or more exec blocks before this block, in the same environment.

:editor: [ID]#

Display the exec block in an editor. If ID is provided, the content of the editor is saved in browser local storage and restored on reload. ID must be unique across all documents, e.g. a UUID.

:include: path [path...] (relative paths)#

Prepend the content of one or more files to the block's content.

:output-style: property: value; [property: value; ...]#

CSS styles to apply to the output generated by the code block, e.g. max-height: 10rem. To which element the styles are applied is runner-specific.

:style: property: value; [property: value; ...]#

CSS styles to apply to the code block and editor, e.g. max-height: 20rem.

:then: name [name...]#

Execute one or more exec blocks after this block, in the same environment.

:when: value (click | load | never)#

Determine when the block's code is executed: on user request (click, the default), when the page loads (load) or not at all (never).

Trigger#

By default, exec blocks are executed on click (:when: click), with controls displayed next to the block.

select * from countries where country_code = 'LI';

They can also be executed immediately on load (:when: load) or not at all (:when: never, useful for definitions that are referenced by other blocks). The controls displayed depend on the type of block.

select * from countries where country_code = 'LI';

Editor#

Blocks can be made editable with the :editor: option.

select * from countries
  where population > 10000000
  order by country_code;

The option takes an optional editor ID. If provided, the content of the editor is saved in browser local storage, and restored on page reload.


Sequencing#

The :after: option allows referencing one or more exec blocks on the same page to be executed before the block, in the same environment. The referenced blocks can themselves have :after: options, forming a dependency graph.

Similarly, the :then: option allows referencing one or more exec blocks to be executed after the block. Unlike :after:, only the blocks referenced by the :then: option of the block itself are executed; the :then: options of referenced blocks are ignored.

If a block appears more than once in the graph, only the first occurrence is executed.

-- :name: sql-people
create table people (first_name text not null, last_name text not null);
-- :name: sql-people-select
select * from people;
-- :after: sql-people
-- :then: sql-people-select
insert into people values ('Joe', 'Bar'), ('Jack', 'Sparrow');

Include file content#

The :include: option allows including the content of one or more external files. The content of the files is prepended to the block's content.

create table people (name text not null, height real, favorite_food text);
insert into people values
  ('Joe', 1.83, null),
  ('Jack', 1.55, 'burgers'),
  ('Jim', null, 'pizza'),
  ('Anthony', 1.78, null);
select * from people;

Runners#

HTML#

The {exec} html runner displays a complete HTML document as an <iframe>, with limited browsing functionality.

MicroPython#

The {exec} micropython runner connects to an embedded system running MicroPython. The code can either be run from RAM (transient, programs disappear on reset) or be written to the file main.py in flash memory (permanent, runs at boot-time).

Note

The micropython runner only works on devices supporting the WebSerial API. Currently, this limits the use to Chromium-based browsers (e.g. Chrome, Edge).

The target device must already be programmed with a MicroPython firmware. The procedure depends on the target device type.

  • BBC micro:bit V2: Download the .hex file for the latest release. Connect the target and mount its filesystem, then copy the file to it.

  • Raspberry Pi Pico: Follow the documentation to download the appropriate firmware file and program the device.

To enable connecting to a target device, it must first be paired with the browser. Connect the target via USB, select " Connect" in the "Tools" menu (), and select the device in the list. This needs to be done only once; once a device is paired, it will be connected automatically when the page loads, unless multiple paired devices are available.

To run a program from RAM, click the "Run" button (). Text input can be sent to the target via the input field, and the program can be interrupted with the "Stop" button (). When the program terminates, the target returns to the REPL and accepts further commands.

To write a program permanently to a target's flash memory, select " Write to main.py" in the "Tools" menu. The program can be removed again with " Remove main.py".

Python#

The {exec} python runner executes code through Pyodide. Each environment uses a distinct, single-threaded interpreter, and all Python blocks specifying the same environment execute on the same interpreter.

The interpreter for the main environment runs on the main browser thread. Interpreter initialization, as well as blocking Python code, will therefore block the main thread and affect rendering, user input, etc. It should be used only when really necessary, e.g. for code using Pygame. All other interpreters run in their own web worker, and don't block user interactions.

Pyodide can be configured via the exec:python: metadata. This enables the following functionality:

  • Load packages: The packages key is a list of package references, either package names or URLs referencing wheels.

  • Copy files to the filesystem: The files key is a mapping of URL to target path. Relative URLs are resolved relative to the _static directory. Relative target paths are resolved relative to $HOME (/home/pyodide). If the target path ends with a /, the filename part of the URL is used as the target filename.

exec:
  python:
    packages: [sqlite3]
    files:
      input.txt:                    # .../_static/input.txt => $HOME/input.txt
      db/init.sql: /tmp/            # .../_static/db/init.sql => /tmp/init.sql
      ../index.html: homepage.html  # .../index.html => $HOME/homepage.html

Synchronous calls to async functions#

async functions can be invoked synchronously with pyodide.ffi.run_sync(). This requires the experimental WebAssembly JavaScript promise integration API (JSPI), which isn't widely supported yet.

  • Chromium-based browsers (Google Chrome, Microsoft Edge): The serving domain must be registered for the origin trial. For local testing, the feature can be enabled with a flag (chrome://flags/#enable-experimental-webassembly-jspi).

  • Firefox: Firefox doesn't have an origin trial yet, and is still working on the implementation. For local testing, the feature can be enabled with a config (javascript.options.wasm_js_promise_integration), but it doesn't work with Pyodide yet.

  • Safari: Safari doesn't currently support JSPI.

Note

Using pyodide.ffi.run_sync() instead of async concurrency prevents interrupting running code via the button.

SQL#

The {exec} sql runner uses a WebAssembly build of SQLite. Each block execution is performed against a new, empty database.