Built-in APIs
PyScript makes available convenience objects, functions and attributes.
In Python this is done via the builtin pyscript
module:
In HTML this is done via py-*
and mpy-*
attributes (depending on the
interpreter you're using):
<button id="foo" py-click="handler_defined_in_python">Click me</button>
These APIs will work with both Pyodide and Micropython in exactly the same way.
Info
Both Pyodide and MicroPython provide access to two further lower-level APIs:
- Access to
JavaScript's
globalThis
via importing thejs
module:import js
(nowjs
is a proxy forglobalThis
in which all native JavaScript based browser APIs are found). - Access to interpreter specific versions of utilities and the foreign function interface. Since these are different for each interpreter, and beyond the scope of PyScript's own documentation, please check each project's documentation (Pyodide / MicroPython) for details of these lower-level APIs.
PyScript can run in two contexts: the main browser thread, or on a web worker. The following three categories of API functionality explain features that are common for both main thread and worker, main thread only, and worker only. Most features work in both contexts in exactly the same manner, but please be aware that some are specific to either the main thread or a worker context.
Common features
These Python objects / functions are available in both the main thread and in code running on a web worker:
pyscript.config
A Python dictionary representing the configuration for the interpreter.
from pyscript import config
# It's just a dict.
print(config.get("files"))
# This will be either "mpy" or "py" depending on the current interpreter.
print(config["type"])
Info
The config
object will always include a type
attribute set to either
mpy
or py
, to indicate which version of Python your code is currently
running in.
Warning
Changing the config
dictionary at runtime has no effect on the actual
configuration.
It's just a convenience to read the configuration at run time.
pyscript.current_target
A utility function to retrieve the unique identifier of the element used
to display content. If the element is not a <script>
and it already has
an id
, that id
will be returned.
<!-- current_target(): explicit-id -->
<mpy-script id="explicit-id">
from pyscript import display, current_target
display(f"current_target(): {current_target()}")
</mpy-script>
<!-- current_target(): mpy-0 -->
<mpy-script>
from pyscript import display, current_target
display(f"current_target(): {current_target()}")
</mpy-script>
<!-- current_target(): mpy-1 -->
<!-- creates right after the <script>:
<script-py id="mpy-1">
<div>current_target(): mpy-1</div>
</script-py>
-->
<script type="mpy">
from pyscript import display, current_target
display(f"current_target(): {current_target()}")
</script>
Note
The return value of current_target()
always references a visible element
on the page, not at the current <script>
that is executing the code.
To reference the <script>
element executing the code, assign it an id
:
Then use the standard document.getElementById(script_id)
function to
return a reference to it in your code.
pyscript.display
A function used to display content. The function is intelligent enough to introspect the object[s] it is passed and work out how to correctly display the object[s] in the web page based on the following mime types:
text/plain
to show the content as texttext/html
to show the content as HTMLimage/png
to show the content as<img>
image/jpeg
to show the content as<img>
image/svg+xml
to show the content as<svg>
application/json
to show the content as JSONapplication/javascript
to put the content in<script>
(discouraged)
The display
function takes a list of *values
as its first argument, and has
two optional named arguments:
target=None
- the DOM element into which the content should be placed. If not specified, thetarget
will use thecurrent_script()
returned id and populate the related dedicated node to show the content.append=True
- a flag to indicate if the output is going to be appended to thetarget
.
There are some caveats:
- When used in the main thread, the
display
function automatically uses the current<py-script>
or<mpy-script>
tag as thetarget
into which the content will be displayed. - If the
<script>
tag has thetarget
attribute, and is not a worker, the element on the page with that ID (or which matches that selector) will be used to display the content instead. - When used in a worker, the
display
function needs an explicittarget="dom-id"
argument to identify where the content will be displayed. - In both the main thread and worker,
append=True
is the default behaviour.
<!-- will produce
<py-script>PyScript</py-script>
-->
<py-script worker>
from pyscript import display
display("PyScript", append=False)
</py-script>
<!-- will produce
<script type="py">...</script>
<script-py>PyScript</script-py>
-->
<script type="py">
from pyscript import display
display("PyScript", append=False)
</script>
<!-- will populate <h1>PyScript</h1> -->
<script type="py" target="my-h1">
from pyscript import display
display("PyScript", append=False)
</script>
<h1 id="my-h1"></h1>
<!-- will populate <h2>PyScript</h2> -->
<script type="py" worker>
from pyscript import display
display("PyScript", target="my-h2", append=False)
</script>
<h2 id="my-h2"></h2>
pyscript.document
On both main and worker threads, this object is a proxy for the web page's
document object.
The document
is a representation of the
DOM
and can be used to read or manipulate the content of the web page.
pyscript.fetch
A common task is to fetch
data from the web via HTTP requests. The
pyscript.fetch
function provides a uniform way to achieve this in both
Pyodide and MicroPython. It is closely modelled on the
Fetch API found
in browsers with some important Pythonic differences.
The simple use case is to pass in a URL and await
the response. If this
request is in a function, that function should also be defined as async
.
from pyscript import fetch
response = await fetch("https://example.com")
if response.ok:
data = await response.text()
else:
print(response.status)
The object returned from an await fetch
call will have attributes that
correspond to the
JavaScript response object.
This is useful for getting response codes, headers and other metadata before
processing the response's data.
Alternatively, rather than using a double await
(one to get the response, the
other to grab the data), it's possible to chain the calls into a single
await
like this:
from pyscript import fetch
data = await fetch("https://example.com").text()
The following awaitable methods are available to you to access the data returned from the server:
arrayBuffer()
returns a Python memoryview of the response. This is equivalent to thearrayBuffer()
method in the browser basedfetch
API.blob()
returns a JavaScriptblob
version of the response. This is equivalent to theblob()
method in the browser basedfetch
API.bytearray()
returns a Pythonbytearray
version of the response.json()
returns a Python datastructure representing a JSON serialised payload in the response.text()
returns a Python string version of the response.
The underlying browser fetch
API has
many request options
that you should simply pass in as keyword arguments like this:
from pyscript import fetch
result = await fetch("https://example.com", method="POST", body="HELLO").text()
Danger
You may encounter CORS errors (especially with reference to a missing Access-Control-Allow-Origin header.
This is a security feature of modern browsers where the site to which you are making a request will not process a request from a site hosted at another domain.
For example, if your PyScript app is hosted under example.com
and you
make a request to bbc.co.uk
(who don't allow requests from other domains)
then you'll encounter this sort of CORS related error.
There is nothing PyScript can do about this problem (it's a feature, not a bug). However, you could use a pass-through proxy service to get around this limitation (i.e. the proxy service makes the call on your behalf).
pyscript.ffi
The pyscript.ffi
namespace contains foreign function interface (FFI) methods
that work in both Pyodide and MicroPython.
pyscript.ffi.create_proxy
A utility function explicitly for when a callback function is added via an
event listener. It ensures the function still exists beyond the assignment of
the function to an event. Should you not create_proxy
around the callback
function, it will be immediately garbage collected after being bound to the
event.
Warning
There is some technical complexity to this situation, and we have attempted
to create a mechanism where create_proxy
is never needed.
Pyodide expects the created proxy to be explicitly destroyed when it's
not needed / used anymore. However, the underlying proxy.destroy()
method
has not been implemented in MicroPython (yet).
To simplify this situation and automatically destroy proxies based on JavaScript memory management (garbage collection) heuristics, we have introduced an experimental flag:
This flag ensures the proxy creation and destruction process is managed for
you. When using this flag you should never need to explicitly call
create_proxy
.
The technical details of how this works are described here.
pyscript.ffi.to_js
A utility function to convert Python references into their JavaScript
equivalents. For example, a Python dictionary is converted into a JavaScript
object literal (rather than a JavaScript Map
), unless a dict_converter
is explicitly specified and the runtime is Pyodide.
The technical details of how this works are described here.
pyscript.js_modules
It is possible to define JavaScript modules to use within your Python code.
Such named modules will always then be available under the
pyscript.js_modules
namespace.
Warning
Please see the documentation (linked above) about restrictions and gotchas when configuring how JavaScript modules are made available to PyScript.
pyscript.storage
The pyscript.storage
API wraps the browser's built-in
IndexDB
persistent storage in a synchronous Pythonic API.
Info
The storage API is persistent per user tab, page, or domain, in the same way IndexedDB persists.
This API is not saving files in the interpreter's virtual file system nor onto the user's hard drive.
from pyscript import storage
# Each store must have a meaningful name.
store = await storage("my-storage-name")
# store is a dictionary and can now be used as such.
The returned dictionary automatically loads the current state of the referenced IndexDB. All changes are automatically queued in the background.
# This is a write operation.
store["key"] = value
# This is also a write operation (it changes the stored data).
del store["key"]
Should you wish to be certain changes have been synchronized to the underlying
IndexDB, just await store.sync()
.
Common types of value can be stored via this API: bool
, float
, int
, str
and None
. In addition, data structures like list
, dict
and tuple
can
be stored.
Warning
Because of the way the underlying data structure are stored in IndexDB,
a Python tuple
will always be returned as a Python list
.
It is even possible to store arbitrary data via a bytearray
or
memoryview
object. However, there is a limitation that such values must be
stored as a single key/value pair, and not as part of a nested data
structure.
Sometimes you may need to modify the behaviour of the dict
like object
returned by pyscript.storage
. To do this, create a new class that inherits
from pyscript.Storage
, then pass in your class to pyscript.storage
as the
storage_class
argument:
from pyscript import window, storage, Storage
class MyStorage(Storage):
def __setitem__(self, key, value):
super().__setitem__(key, value)
window.console.log(key, value)
...
store = await storage("my-data-store", storage_class=MyStorage)
# The store object is now an instance of MyStorage.
@pyscript/core/donkey
Sometimes you need a Python worker ready and waiting to evaluate any code on
your behalf. This is the concept behind the JavaScript "donkey". We couldn't
think of a better way than "donkey" to describe something that is easy to
understand and shoulders the burden without complaint. This feature
means you're able to use PyScript without resorting to specialised
<script type="py">
style tags. It's just vanilla JavaScript.
Simply import { donkey } from '@pyscript/core/dist/core.js'
and automatically
have both a pyscript module running on your page and a utility to bootstrap a
terminal based worker to evaluate any Python code as and when needed in the
future.
import { donkey } from '@pyscript/core/dist/core.js';
const {
process, // process(code) code (visible in the terminal)
execute, // execute(statement) in Python exec way
evaluate, // evaluate(expression) in Python eval way
clear, // clear() the terminal
reset, // reset() the terminal (including colors)
kill, // kill() the worker forever
} = donkey({
type: 'py' || 'mpy', // the Python interpreter to run
persistent: false, // use `true` to track globals and locals
terminal: '', // optionally set a target terminal container
config: {}, // the worker config (packages, files, etc.)
});
By default PyScript creates a target terminal. If you don't want a terminal to
appear on your page, use the terminal
option to point to a CSS addressable
container that is not visible (i.e. the target has display: none
).
@pyscript/core/dist/storage.js
The equivalent functionality based on the JS module can be found through our module.
The goal is to be able to share the same database across different worlds (interpreters) and the functionality is nearly identical except there is no class to provide because the storage in JS is just a dictionary proxy that synchronizes behind the scene all read, write or delete operations.
pyscript.web
The classes and references in this namespace provide a Pythonic way to interact with the DOM. An explanation for how to idiomatically use this API can be found in the user guide
pyscript.web.page
This object represents a web page. It has four attributes and two methods:
html
- a reference to a Python object representing the document's html root element.head
- a reference to a Python object representing the document's head.body
- a reference to a Python object representing the document's body.title
- the page's title (usually displayed in the browser's title bar or a page's tab.find
- a method that takes a single selector argument and returns a collection of Python objects representing the matching elements.append
- a shortcut forpage.body.append
(to add new elements to the page).
You may also shortcut the find
method by enclosing a CSS selector in square
brackets: page["#my-thing"]
.
These are provided as a convenience so you have several simple and obvious options for accessing and changing the content of the page.
All the Python objects returned by these attributes and method are instances of
classes relating to HTML elements defined in the pyscript.web
namespace.
pyscript.web.*
There are many classes in this namespace. Each is a one-to-one mapping of any HTML element name to a Python class representing the HTML element of that name. Each Python class ensures only valid properties and attributes can be assigned, according to web standards.
Usage of these classes is explained in the user guide.
Info
The full list of supported element/class names is:
grid
a, abbr, address, area, article, aside, audio
b, base, blockquote, body, br, button
canvas, caption, cite, code, col, colgroup
data, datalist, dd, del_, details, dialog, div, dl, dt
em, embed
fieldset, figcaption, figure, footer, form
h1, h2, h3, h4, h5, h6, head, header, hgroup, hr, html
i, iframe, img, input_, ins
kbd
label, legend, li, link
main, map_, mark, menu, meta, meter
nav
object_, ol, optgroup, option, output
p, param, picture, pre, progress
q
s, script, section, select, small, source, span, strong, style, sub, summary, sup
table, tbody, td, template, textarea, tfoot, th, thead, time, title, tr, track
u, ul
var, video
wbr
These correspond to the standard
HTML elements
with the caveat that del_
and input_
have the trailing underscore
(_
) because they are also keywords in Python, and the grid
is a custom
class for a div
with a grid
style display
property.
All these classes ultimately derive from the
pyscript.web.elements.Element
base class.
In addition to properties defined by the HTML standard for each type of HTML
element (e.g. title
, src
or href
), all elements have the following
properties and methods (in alphabetical order):
append(child)
- add thechild
element to the element's children.children
- a collection containing the element's child elements (that it contains).classes
- a set of CSS classes associated with the element.clone(clone_id=None)
- Make a clone of the element (and the underlying DOM object), and assign it the optionalclone_id
.find(selector)
- use a CSS selector to find matching child elements.parent
- the element's parent element (that contains it).show_me
- scroll the element into view.style
- a dictionary of CSS style properties associated with the element.update(classes=None, style=None, **kwargs)
- update the element with the specified classes (set), style (dict) and DOM properties (kwargs)._dom_element
- a reference to the proxy object that represents the underlying native HTML element.
Info
All elements, by virtue of inheriting from the base Element
class, may
have the following properties:
The classes
set-like object has the following convenience functions:
add(*class_names)
- add the class(es) to the element.contains(class_name)
- indicate ifclass_name
is associated with the element.remove(*class_names)
- remove the class(es) from the element.replace(old_class, new_class)
- replace theold_class
withnew_class
.toggle(class_name)
- add a class if it is absent, or remove a class if it is present.
Elements that require options (such as the datalist
, optgroup
and select
elements), can have options passed in when they are created:
Notice how options can be a tuple of two values (the name and associated value) or just the single name (whose associated value will default to the given name).
It's possible to access and manipulate the options
of the resulting elements:
selected_option = my_select.options.selected
my_select.options.remove(0) # Remove the first option (in position 0).
my_select.clear()
my_select.options.add(html="Orange")
Finally, the collection of elements returned by find
and children
is
iterable, indexable and sliceable:
Furthermore, four attributes related to all elements contained in the collection can be read (as a list) or set (applied to all contained elements):
classes
- the list of classes associated with the elements.innerHTML
- the innerHTML of each element.style
- a dictionary like object for interacting with CSS style rules.value
- thevalue
attribute associated with each element.
pyscript.when
A Python decorator to indicate the decorated function should handle the specified events for selected elements.
The decorator takes two parameters:
- The
event_type
should be the name of the browser event to handle as a string (e.g."click"
). - The
selector
should be a string containing a valid selector to indicate the target elements in the DOM whose events ofevent_type
are of interest.
The following example has a button with an id of my_button
and a decorated
function that handles click
events dispatched by the button.
from pyscript import when, display
@when("click", "#my_button")
def click_handler(event):
"""
Event handlers get an event object representing the activity that raised
them.
"""
display("I've been clicked!")
This functionality is related to the py-*
or mpy-*
HTML attributes.
pyscript.window
On the main thread, this object is exactly the same as import js
which, in
turn, is a proxy of JavaScript's
globalThis
object.
On a worker thread, this object is a proxy for the web page's global window context.
Warning
The reference for pyscript.window
is always a reference to the main
thread's global window context.
If you're running code in a worker this is not the worker's own global
context. A worker's global context is always reachable via import js
(the js
object being a proxy for the worker's globalThis
).
pyscript.HTML
A class to wrap generic content and display it as un-escaped HTML on the page.
<script type="mpy">
from pyscript import display, HTML
# Escaped by default:
display("<em>em</em>") # <em>em</em>
</script>
<script type="mpy">
from pyscript import display, HTML
# Un-escaped raw content inserted into the page:
display(HTML("<em>em</em>")) # <em>em</em>
</script>
pyscript.RUNNING_IN_WORKER
This constant flag is True
when the current code is running within a
worker. It is False
when the code is running within the main thread.
pyscript.WebSocket
If a pyscript.fetch
results in a call and response HTTP interaction with a
web server, the pyscript.Websocket
class provides a way to use
websockets
for two-way sending and receiving of data via a long term connection with a
web server.
PyScript's implementation, available in both the main thread and a web worker, closely follows the browser's own WebSocket class.
This class accepts the following named arguments:
- A
url
pointing at the ws or wss address. E.g.:WebSocket(url="ws://localhost:5037/")
- Some
protocols
, an optional string or a list of strings as described here.
The WebSocket
class also provides these convenient static constants:
WebSocket.CONNECTING
(0
) - thews.readyState
value when a web socket has just been created.WebSocket.OPEN
(1
) - thews.readyState
value once the socket is open.WebSocket.CLOSING
(2
) - thews.readyState
afterws.close()
is explicitly invoked to stop the connection.WebSocket.CLOSED
(3
) - thews.readyState
once closed.
A WebSocket
instance has only 2 methods:
ws.send(data)
- wheredata
is either a string or a Python buffer, automatically converted into a JavaScript typed array. This sends data via the socket to the connected web server.ws.close(code=0, reason="because")
- which optionally acceptscode
andreason
as named arguments to signal some specific status or cause for closing the web socket. Otherwisews.close()
works with the default standard values.
A WebSocket
instance also has the fields that the JavaScript
WebSocket
instance will have:
- binaryType - the type of binary data being received over the WebSocket connection.
- bufferedAmount -
a read-only property that returns the number of bytes of data that have been
queued using calls to
send()
but not yet transmitted to the network. - extensions - a read-only property that returns the extensions selected by the server.
- protocol - a read-only property that returns the name of the sub-protocol the server selected.
- readyState -
a read-only property that returns the current state of the WebSocket
connection as one of the
WebSocket
static constants (CONNECTING
,OPEN
, etc...). - url -
a read-only property that returns the absolute URL of the
WebSocket
instance.
A WebSocket
instance can have the following listeners. Directly attach
handler functions to them. Such functions will always receive a single
event
object.
- onclose -
fired when the
WebSocket
's connection is closed. - onerror - fired when the connection is closed due to an error.
- onmessage -
fired when data is received via the
WebSocket
. If theevent.data
is a JavaScript typed array instead of a string, the reference it will point directly to a memoryview of the underlyingbytearray
data. - onopen - fired when the connection is opened.
The following code demonstrates a pyscript.WebSocket
in action.
<script type="mpy" worker>
from pyscript import WebSocket
def onopen(event):
print(event.type)
ws.send("hello")
def onmessage(event):
print(event.type, event.data)
ws.close()
def onclose(event):
print(event.type)
ws = WebSocket(url="ws://localhost:5037/")
ws.onopen = onopen
ws.onmessage = onmessage
ws.onclose = onclose
</script>
Info
It's also possible to pass in any handler functions as named arguments when
you instantiate the pyscript.WebSocket
class:
pyscript.js_import
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.
<script type="py">
from pyscript import js_import, window
escaper, = await js_import("https://esm.run/html-escaper")
window.console.log(escaper)
The js_import
call returns an asynchronous tuple containing the JavaScript
modules referenced as string arguments.
pyscript.py_import
Warning
This is an experimental feature.
Feedback and bug reports are welcome!
If you have a lot of Python packages referenced in your configuration, startup performance may be degraded as these are downloaded.
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.
<script type="py">
from pyscript import py_import
matplotlib, regex, = await py_import("matplotlib", "regex")
print(matplotlib, regex)
</script>
The py_import
call returns an asynchronous tuple containing the Python
modules provided by the packages referenced as string arguments.
Main-thread only features
pyscript.PyWorker
A class used to instantiate a new worker from within Python.
Note
Sometimes we disambiguate between interpreters through naming conventions
(e.g. py
or mpy
).
However, this class is always PyWorker
and the desired interpreter
MUST be specified via a type
option. Valid values for the type of
interpreter are either micropython
or pyodide
.
The following fragments demonstrate how to evaluate the file worker.py
on a
new worker from within Python.
from pyscript import RUNNING_IN_WORKER, display, sync
display("Hello World", target="output", append=True)
# will log into devtools console
print(RUNNING_IN_WORKER) # True
print("sleeping")
sync.sleep(1)
print("awake")
from pyscript import PyWorker
# type MUST be either `micropython` or `pyodide`
PyWorker("worker.py", type="micropython")
<script type="mpy" src="./main.py">
<div id="output"></div> <!-- The display target -->
pyscript.workers
The pyscript.workers
reference allows Python code in the main thread to
easily access named workers (and their exported functionality).
For example, the following Pyodide code may be running on a named worker
(see the name
attribute of the script
tag):
<script type="py" worker name="py-version">
import sys
def version():
return sys.version
# define what to export to main consumers
__export__ = ["version"]
</script>
While over on the main thread, this fragment of MicroPython will be able to
access the worker's version
function via the workers
reference:
<script type="mpy">
from pyscript import workers
pyworker = await workers["py-version"]
# print the pyodide version
print(await pyworker.version())
</script>
Importantly, the workers
reference will NOT provide a list of
known workers, but will only await
for a reference to a named worker
(resolving when the worker is ready). This is because the timing of worker
startup is not deterministic.
Should you wish to await for all workers on the page at load time, it's possible to loop over matching elements in the document like this:
<script type="mpy">
from pyscript import document, workers
for el in document.querySelectorAll("[type='py'][worker][name]"):
await workers[el.getAttribute('name')]
# ... rest of the code
</script>
Worker only features
pyscript.sync
A function used to pass serializable data from workers to the main thread.
Imagine you have this code on the main thread:
from pyscript import PyWorker
def hello(name="world"):
display(f"Hello, {name}")
worker = PyWorker("./worker.py")
worker.sync.hello = hello
In the code on the worker, you can pass data back to handler functions like this:
HTML attributes
As a convenience, and to ensure backwards compatibility, PyScript allows the use of inline event handlers via custom HTML attributes.
Warning
This classic pattern of coding (inline event handlers) is no longer considered good practice in web development circles.
We include this behaviour for historic reasons, but the folks at Mozilla have a good explanation of why this is currently considered bad practice.
These attributes, expressed as py-*
or mpy-*
attributes of an HTML element,
reference the name of a Python function to run when the event is fired. You
should replace the *
with the actual name of an event (e.g. py-click
or
mpy-click
). This is similar to how all
event handlers on elements
start with on
in standard HTML (e.g. onclick
). The rule of thumb is to
simply replace on
with py-
or mpy-
and then reference the name of a
Python function.
<button py-click="handle_click" id="my_button">Click me!</button>
from pyscript import window
def handle_click(event):
"""
Simply log the click event to the browser's console.
"""
window.console.log(event)
Under the hood, the pyscript.when
decorator is used to
enable this behaviour.
Note
In earlier versions of PyScript, the value associated with the attribute was simply evaluated by the Python interpreter. This was unsafe: manipulation of the attribute's value could have resulted in the evaluation of arbitrary code.
This is why we changed to the current behaviour: just supply the name of the Python function to be evaluated, and PyScript will do this safely.