Builtin helpers
PyScript makes available convenience objects, functions and attributes.
In Python this is done via the 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>
Common features
These Python objects / functions are available in both the main thread and in code running on a web worker:
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.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.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.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.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.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. Remember, in
order to use await
you must have the async
attribute in the script
tag
that references your code. 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.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.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.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.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.config
A Python dictionary representing the configuration for the interpreter.
from pyscript import config
# It's just a dict.
print(config.get("files"))
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.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.
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 -->
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:
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" async>
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.
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" async>
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.
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.