PyScript :footcite:p:`merket22_pyscript`
========================================
`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 :html:`
` section:
.. code-block:: html
...
...
Python code is inserted between :html:`` tags within the HTML's body:
.. code-block:: html
...
print("Hello World")
Apart from the Python Standard Library, Pyodide includes a lot of additional packages,
which must be imported for usage, using the :html:`` tags:
.. code-block:: html
...
- numpy
- matplotlib
import numpy
import matplotlib
...
Please note, that the imported modules must also be imported into your Python code
using the :python:`import` statement. A list of all supported packages can be found
`here `_.
.. _PyScript: https://pyscript.net/
.. _Pyodide: https://pyodide.org/en/stable/index.html
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:
.. code-block:: html
...
from js import document, console
table = document.getElementById("some_table").childNodes
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 :javascript:`addEventListener()`.
For this, pyodide offers the function :python:`create_proxy()` to wrap a Python
function into a JavaScript function:
.. code-block:: python
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:
.. code-block:: python
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.
.. _JavaScript global namespace: https://developer.mozilla.org/en-US/docs/Web/API#interfaces
.. _Document: https://developer.mozilla.org/en-US/docs/Web/API/Document
.. _console: https://developer.mozilla.org/en-US/docs/Web/API/console
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 :python:`pyfetch()` method
for this.
Since Ajax calls are asynchronous by nature, :python:`pyfetch()` can only be called from
within asynchronous functions (:python:`async def`). As those don't block the code, calling
:python:`await` to wait for the response is fine:
.. code-block:: python
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 :python:`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 :python:`pick_data()`:
.. code-block:: python
async def pick_data():
data = await load_resource()
data_value = random.choice(data)
console.log(f"Value: {dat_value}")
Same as the :python:`load_resource()` function, the :python:`pick_data()` function is
defined as an asynchronous function, as it needs to wait for :python:`load_resource()`.
In call those methods in a way, that they don't block the code, an old JavaScript trick
can be applied:
.. code-block:: python
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 :python:`load_resource()`
to finish. The method is executed in parallel.
.. _requests: https://requests.readthedocs.io/en/latest/
Other stuff
-----------
PyScript provides a :python:`write()` method, which sends strings into labeled elements
on the page. No import statement is necessary:
.. code-block:: python
element = document.getElementById("my_id")
pyscript.write("element", "Hello World")
Further Resources
-----------------
* `Tutorial from RealPython `_
* `Tutorial from ct (German) <../../../_static/ct.22.17.154-159.pdf>`__
.. footbibliography::