Intro to PyScript: Run Python in your web browser


PyScript, created by Anaconda, is an experimental but promising new technology that makes the Python runtime available as a scripting language in WebAssembly-enabled browsers.

Every modern, commonly used browser now supports WebAssembly, the high-speed runtime standard that many languages (like C, C++, and Rust) can compile to. Python’s reference implementation is written in C, and one earlier project, Pyodide, provided a WebAssembly port of the Python runtime.

PyScript, though, aims to provide a whole in-browser environment for running Python as a web scripting language. It builds on top of Pyodide but adds or enhances features like the ability to import modules from the standard library, use third-party imports, configure two-way interactions with the Document Object Model (DOM), and do many other things useful in both the Python and JavaScript worlds.

Right now, PyScript is still a prototypical and experimental project. Anaconda doesn’t recommend using it in production. But curious users can try out examples on the PyScript site and use the available components to build experimental Python-plus-JavaScript applications in the browser.

In this article, we’ll take a tour of the basics of PyScript, and see how it allows Python and JavaScript to interact.

Programming with PyScript

At its core, PyScript consists of a single JavaScript include that you can add to a web page. This include loads the base PyScript runtime and automatically adds support for custom tags used in PyScript.

Here is a simple example of a “hello, world” project in PyScript:


<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
        <script defer src="https://pyscript.net/unstable/pyscript.js"></script>
    </head>
    <body>

<py-script output="out">
print("Hello world")
</py-script>

<div id="out"></div>

    </body>

</html>

The script tag in the document’s head loads the core PyScript functionality. The pyscript.css stylesheet is optional, but useful. Among other things, it inserts notices to the user at the page’s load time about what the page is doing—loading the Python runtime, initializing, and so on.

Python code is enclosed in the custom py-script tag. Note that the code should be formatted according to Python’s conventions for indentation, or it won’t run properly. Be aware of this if you use an editor that reformats HTML automatically; it might mangle the contents of the py-script block and make it unrunnable.

Any Python code is evaluated once the PyScript components finish loading. If the script in the tags writes to stdout (as with a print) statement, you can direct where on the page to show the output by supplying an output property. In this example, stdout for the script gets directed into the div with the ID "out".

If you save this into a file and open it in a web browser, you’ll first see a “loading” indicator and a pause, as the browser obtains the PyScript runtime and sets it up. The runtime should remain cached on future loads but will still take a moment to activate. After that, Hello world should appear on the page.

Standard library imports

Scripts using Python’s builtins alone are only somewhat useful. Python’s standard library is available in PyScript the same way you’d use it in regular Python: simply import and get to work. Standard library imports should just work in PyScript.

If you wanted to modify the above script block to display the current time, you wouldn’t need to do it any differently than you would in conventional Python:


import datetime
print ("Current date and time:",
datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"))

Using libraries from PyPI

What if we want to install a package from PyPI and use that? PyScript has another tag, py-env, that specifies third-party packages need to be installed. Let’s replace the py-script block in the original script with these two blocks:


<py-env>
- humanize
</py-env>

<py-script output="out">
from datetime import datetime
import humanize
now_int = int(datetime.timestamp(datetime.now()))
now_fmt = humanize.intcomma(now_int)
print("It has been", now_fmt, "seconds since the epoch.")
</py-script>

The py-env block lets us list packages to add, in the same way we might list them in a requirements.txt file for a Python project. We can then import and use them as we would any other Python package. In this example, we’re using a third-party package called humanize to make numerical output easier to read.

Note that not all packages from PyPI will install and run as expected. For instance, requests requires access to networking components that aren’t yet supported. (A possible workaround for this issue is to use pyodide.http.pyfetch, which is supported natively.) But pure Python packages, like humanize, should run fine. And packages used in the examples provided by Anaconda, like numpy, pandas, bokeh, or matplotlib, will also work.

Importing locally

For another common scenario, let’s say you want to import from other Python scripts in the same directory tree as your web page. Using imports makes it easier to move more of your Python logic out of the web page itself, where it’s intermixed with your presentation and may become difficult to work with.

Normally, Python uses the presence of other .py files in the file system as indications of what it can import. PyScript can’t work this way, so you’ll need to specify which files you want to make available as importable modules.

Let’s say you have a web page named index.html in a given directory on your web server, and you want to place a Python file named main.py next to it. This way your in-page script can be just import main, and you can confine the majority of the Python logic to the actual .py files.

Specify the Python files you want to make importable in your py-env block:


- paths:

    - ./main.py

This would allow main.py, in the same web server directory as the web page itself, to be importable with import main.

An important thing to keep in mind: You can’t perform imports like this on a web page you’ve launched locally in the browser. This is due to restrictions on file system access imposed by the WebAssembly runtime and the browser itself. Instead, you’d need to host the pages on a web server to serve the web page and the .py file.

The REPL tag

Python users ought to be familiar with Jupyter Notebook, the in-browser live coding environment for Python typically used for mathematics and statistics. PyScript offers a primitive building block for such an environment, the py-repl tag.

py-repl generates an input field on a web page that functions like a very basic version of a Jupyter Notebook environment. Here’s an example from Anaconda’s own demos:


<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
  </head>

  <body>
    <h1><b>pyscript REPL</b></h1>
    Tip: press Shift-ENTER to evaluate a cell
    <br>
    <div>
      <py-repl id="my-repl" auto-generate="true"> </py-repl>
    </div>
  </body>
</html>

Run this code and you’ll be presented with an input field, which works like the Python REPL.

Currently, the REPL tag has very little in the way of documented customization. For instance, if you want to programmatically access the contents of a cell or its results, there’s no clear documentation for how to do that.

PyScript's REPL component. IDG

PyScript’s Jupyter-like REPL component lets you run Python interactively in a page, although it’s not yet very flexible or configurable.

Interacting with JavaScript event listeners

Because PyScript is based on pyodide, it uses pyodide‘s mechanisms for interacting with the DOM. For instance, if we wanted to get the value of an input box on a web page and use it in our Python code, we’d do this:


<input id="txt">

<py-script>
from js import document, console
from pyodide import create_proxy

def _eventlog(e):
    console.log(f"Input value: {e.target.value}")

eventlog = create_proxy(_eventlog)

document.getElementById("txt").addEventListener("input", eventlog)
</py-script>

The js library provides a Python interface to many common JavaScript entities, like the document and console objects. They behave almost exactly the same way in PyScript as they do in JavaScript. The create_proxy function in pyodide lets us take a Python function object and generate a JavaScript interface for it, so it can be used as the event listener for the input box. Any keystrokes in the input box are logged to the console, but they can also be handled on the Python side.

Copyright © 2022 IDG Communications, Inc.



Source link