PyScript [1]
PyScript is a framework, which enables using Python instead of JavaScript to program web pages. It acts as a wrapper for Pyodide, a CPython port which translates Python code into WebAssembly, which can be processed by a web browser.
Basic setup
PyScript does not require an installation as the resources are provided by a
Content Delivery Network (CDN) by the PyScript developers. To include PyScript to
a project, add these statements into you <head> section:
<html>
<head>
...
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css"/>
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
</head>
<body>
...
</body>
</html>
Python code is inserted between <py-script> tags within the HTML’s body:
<html>
<head>
...
</head>
<body>
<py-script>
print("Hello World")
<py-script>
</body>
</html>
Apart from the Python Standard Library, Pyodide includes a lot of additional packages,
which must be imported for usage, using the <py-env> tags:
<html>
<head>
...
</head>
<body>
<py-env>
- numpy
- matplotlib
</py-env>
<py-script>
import numpy
import matplotlib
...
<py-script>
</body>
</html>
Please note, that the imported modules must also be imported into your Python code
using the import statement. A list of all supported packages can be found
here.
Accessing the DOM
While PyScript is supposed to be a replacement for JavaScript it is still required to utilize JavaScript to access the DOM, hence accessing HTML objects. For this, Pyodide provides a JavaScript API for Python, which offers access to the entire JavaScript global namespace such as the Document or the console interface:
<html>
...
<py-script>
from js import document, console
table = document.getElementById("some_table").childNodes
</py-script>
<div>
<table id="some_table"></table>
</div>
</html>
As shown, the element’s JavaScript methods can be applied here.
If a Python function wants to interact with the DOM, a function reference is required.
The reason is that JavaScript doesn’t recognize Python functions, only JavaScript functions,
for example Python functions cannot be passed to addEventListener().
For this, pyodide offers the function create_proxy() to wrap a Python
function into a JavaScript function:
from pyodide import create_proxy
def button_clicked(e):
print(e.target.textContent)
js_button_clicked = create_proxy(key_clicked)
The proxy function can the be used by other DOM objects:
from js import document, console
...
button = document.getElementById("some_button")
button.addEventListener("click", js_button_clicked)
As shown above, typical JavaScript data structures such as Event objects can be used in the Python function, just like in JavaScript itself.
Asynchron Loading
Loading external resources, such as a text file, can be downloaded via Ajax, instead of putting
the data straight into the code. In Python, this might be done using the requests
library, which is not available in Pyodide, but it offers the pyfetch() method
for this.
Since Ajax calls are asynchronous by nature, pyfetch() can only be called from
within asynchronous functions (async def). As those don’t block the code, calling
await to wait for the response is fine:
from pyodide.http import pyfetch
from pyodide import JsException
from js import console
async def load_resource():
try:
response = wait pytech(url="/some/url/containing/resources.txt",
method="GET",
headers={"Content-Type": "text/plain"})
if response.ok:
data = await response.string()
console.log(data.split())
return data.split()
except JSException:
return None
The JsException is the base class of any JavaScript error, which is encapsulated
by PyScript. Using a more specific exception type is possible.
The resources can now be further processed, like here using pick_data():
async def pick_data():
data = await load_resource()
data_value = random.choice(data)
console.log(f"Value: {dat_value}")
Same as the load_resource() function, the pick_data() function is
defined as an asynchronous function, as it needs to wait for load_resource().
In call those methods in a way, that they don’t block the code, an old JavaScript trick can be applied:
from js import setTimeout from pyodide import create_proxy ... setTimeout(create_proxy(load_resource), 0)
Setting the timeout to 0 prevents the code from having to wait for load_resource()
to finish. The method is executed in parallel.
Other stuff
PyScript provides a write() method, which sends strings into labeled elements
on the page. No import statement is necessary:
element = document.getElementById("my_id")
pyscript.write("element", "Hello World")