Configuration
The browser tab in which your PyScript based web page is displayed is a very secure sandboxed computing environment for running your Python code.
This is also the case for web workers running Python. Despite being associated with a single web page, workers are completely separate from each other (except for some very limited and clearly defined means of interacting, which PyScript looks after for you).
We need to tell PyScript how we want such Python environments to be configured. This works in the same way for both the main thread and for web workers. Such configuration ensures we get the expected resources ready before our Python code is evaluated (resources such as arbitrary data files, third party Python packages and PyScript plugins).
TOML or JSON
Configuration can be expressed in two formats:
- TOML is the configuration file format preferred by folks in the Python community.
- JSON is a data format most often used by folks in the web community.
Since PyScript is the marriage of Python and the web, and we respect the traditions of both technical cultures, we support both formats.
However, because JSON is built into all browsers by default and TOML requires an additional download of a specialist parser before PyScript can work, the use of JSON is more efficient from a performance point of view.
The following two configurations are equivalent, and simply tell PyScript to ensure the packages arrr and numberwang are installed from PyPI (the Python Packaging Index):
File or inline
The recommended way to write configuration is via a separate file and then reference it from the tag used to specify the Python code:
If you use JSON, you can make it the value of the config
attribute:
<script type="mpy" src="main.py" config='{"packages":["arrr", "numberwang"]}'></script>
For historical and convenience reasons we still support the inline
specification of configuration information via a single <py-config>
or
<mpy-config>
tag in your HTML document:
<py-config>
{
"packages": ["arrr", "numberwang" ]
}
</py-config>
Warning
Should you use <py-config>
or <mpy-config>
, there must be only one of
these tags on the page per interpreter.
Options
There are five core options (interpreter
, files
,
packages
, plugins
and
js_modules
.) The user is free to define
arbitrary additional configuration options that plugins or an app may require
for their own reasons.
Interpreter
The interpreter
option pins the Python interpreter to the version of the
specified value. This is useful for testing (does my code work on a specific
version of Pyodide?), or to ensure the precise combination of PyScript version
and interpreter version are pinned to known values.
The value of the interpreter
option should be a valid version number
for the Python interpreter you are configuring, or a fully qualified URL to
a custom version of the interpreter.
The following two examples are equivalent:
The following JSON fragment uses a fully qualified URL to point to the same version of Pyodide as specified in the previous examples:
{
"interpreter": "https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.mjs"
}
Files
The files
option fetches arbitrary content from URLs onto the filesystem
available to Python, and emulated by the browser. Just map a valid URL to a
destination filesystem path.
The following JSON and TOML are equivalent:
{
"files": {
"https://example.com/data.csv": "./data.csv",
"/code.py": "./subdir/code.py"
}
}
[files]
"https://example.com/data.csv" = "./data.csv"
"/code.py" = "./subdir/code.py"
If you make the target an empty string, the final "filename" part of the source
URL becomes the destination filename, in the root of the filesystem, to which
the content is copied. As a result, the data.csv
entry from the previous
examples could be equivalently re-written as:
{
"files": {
"https://example.com/data.csv": "",
... etc ...
}
}
Warning
PyScript expects all file destinations to be unique.
If there is a duplication PyScript will raise an exception to help you find the problem.
Tip
For most people, most of the time, the simple URL to filename mapping, described above, will be sufficient.
Yet certain situations may require more flexibility. In which case, read on.
Sometimes many resources are needed to be fetched from a single location and
copied into the same directory on the file system. To aid readability and
reduce repetition, the files
option comes with a mini
templating language
that allows re-usable placeholders to be defined between curly brackets ({
and }
). When these placeholders are encountered in the files
configuration,
their name is replaced with their associated value.
Attention
Valid placeholder names are always enclosed between curly brackets
({
and }
), like this: {FROM}
, {TO}
and {DATA SOURCE}
(capitalized names help identify placeholders
when reading code ~ although this isn't strictly necessary).
Any number of placeholders can be defined and used anywhere within URLs and paths that map source to destination.
The following JSON and TOML are equivalent:
{
"files": {
"{DOMAIN}": "https://my-server.com",
"{PATH}": "a/path",
"{VERSION}": "1.2.3",
"{FROM}": "{DOMAIN}/{PATH}/{VERSION}",
"{TO}": "./my_module",
"{FROM}/__init__.py": "{TO}/__init__.py",
"{FROM}/foo.py": "{TO}/foo.py",
"{FROM}/bar.py": "{TO}/bar.py",
"{FROM}/baz.py": "{TO}/baz.py",
}
}
[files]
"{DOMAIN}" = "https://my-server.com"
"{PATH}" = "a/path"
"{VERSION}" = "1.2.3"
"{FROM}" = "{DOMAIN}/{PATH}/{VERSION}"
"{TO}" = "./my_module"
"{FROM}/__init__.py" = "{TO}/__init__.py"
"{FROM}/foo.py" = "{TO}/foo.py"
"{FROM}/bar.py" = "{TO}/bar.py"
"{FROM}/baz.py" = "{TO}/baz.py"
The {DOMAIN}
, {PATH}
, and {VERSION}
placeholders are
used to create a further {FROM}
placeholder. The {TO}
placeholder is also
defined to point to a common sub-directory on the file system. The final four
entries use {FROM}
and {TO}
to copy over four files (__init__.py
,
foo.py
, bar.py
and baz.py
) from the same source to a common destination
directory.
For convenience, if the destination is just a directory (it ends with /
)
then PyScript automatically uses the filename part of the source URL as the
filename in the destination directory.
For example, the end of the previous config file could be:
"{TO}" = "./my_module/"
"{FROM}/__init__.py" = "{TO}"
"{FROM}/foo.py" = "{TO}"
"{FROM}/bar.py" = "{TO}"
"{FROM}/baz.py" = "{TO}"
Packages
The packages
option defines a list of Python packages
to be installed from
PyPI onto the filesystem by Pyodide's
micropip package
installer.
Warning
Because micropip
is a Pyodide-only feature, and MicroPython doesn't
support code packaged on PyPI, the packages
option is only available
for use with Pyodide.
If you need Python modules for MicroPython, use the files option to manually copy the source code onto the file system.
The following two examples are equivalent:
The names in the list of packages
can be any of the following valid forms:
- A name of a package on PyPI:
"snowballstemmer"
- A name for a package on PyPI with additional constraints:
"snowballstemmer>=2.2.0"
- An arbitrary URL to a Python package:
"https://.../package.whl"
- A file copied onto the browser based file system:
"emfs://.../package.whl"
Plugins
The plugins
option lists plugins enabled by PyScript to add extra
functionality to the platform.
Each plugin should be included on the web page, as described in the plugins section. Then the plugin's name should be listed.
Info
The "!error"
syntax is a way to turn off a plugin built into PyScript
that is enabled by default.
Currently, the only built-in plugin is the error
plugin to display a
stack trace and error messages in the DOM. More may be added at a later
date.
JavaScript modules
It's easy to import and use JavaScript modules in your Python code.
Warning
This feature currently only works with Pyodide. MicroPython support will come in a future release.
To do so, requires telling PyScript about the JavaScript modules you want to
use. This is the purpose of the js_modules
related configuration fields.
There are two fields:
js_modules.main
defines JavaScript modules loaded in the context of the main thread of the browser. Helpfully, it is also possible to interact with such modules from the context of workers. Sometimes such modules also need CSS files to work, and these can also be specified.js_modules.worker
defines JavaScript modules loaded into the context of the web worker. Such modules must not expectdocument
orwindow
references (if this is the case,you must load them viajs_modules.main
and use them from the worker). However, if the JavaScript module could work without such references, then performance is better if defined on a worker. Because CSS is meaningless in the context of a worker, it is not possible to specify such files in a worker context.
Once specified, your JavaScript modules will be available under the
pyscript.js_modules.*
namespace.
To specify such modules, simply provide a list of source/module name pairs.
For example, to use the excellent Leaflet JavaScript module for creating interactive maps you'd add the following lines:
[js_modules.main]
"https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet-src.esm.js" = "leaflet"
"https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css" = "leaflet" # CSS
{
"js_modules": {
"main": {
"https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet-src.esm.js": "leaflet",
"https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css": "leaflet"
}
}
}
Info
Notice how the second line references the required CSS needed for the JavaScript module to work correctly.
The CSS file MUST target the very same name as the JavaScript module to which it is related.
Warning
Since the Leaflet module expects to manipulate the DOM and have access to
document
and window
references, it must only be added via the
js_modules.main
setting (as shown) and cannot be added in a worker
context.
At this point Python code running on either the main thread or in a worker will have access to the JavaScript module like this:
from pyscript.js_modules import leaflet as L
map = L.map("map")
# etc....
Some JavaScript modules (such as html-escaper) don't require access to the DOM and, for efficiency reasons, can be included in the worker context:
[js_modules.worker]
"https://cdn.jsdelivr.net/npm/html-escaper" = "html_escaper"
{
"js_modules": {
"worker": {
"https://cdn.jsdelivr.net/npm/html-escaper": "html_escaper"
}
}
}
However, from polyscript.js_modules import html_escaper
would then only work
within the context of Python code running on a worker.
Custom
Sometimes plugins or apps need bespoke configuration options.
So long as you don't cause a name collision with the built-in option names then you are free to use any valid data structure that works with both TOML and JSON to express your configuration needs.
TODO: explain how to programmatically get access to an object representing the config.