Back

PyScript: Python on the Web

PyScript: Python on the Web

If you have been following tech news and updates, you must have heard of PyScript. PyScript is a framework that provides in-browser support for Python through an HTML interface; this means you can use all rich libraries Python has to offer, directly at your HTML interface, without server-side support: in other words, you can code your pages using Python instead of JavaScript!

PyScript provides Python support in the browser using Pyodide (Pyodide makes it possible to install and run Python packages in the browser) and WebAssembly; It allows you to write Python scripts that are compiled into .wasm format that the browser can run. In addition, PyScript enables injecting Python scripts in your HTML file the same way you’d have a JavaScript file or code block. This framework provides a lot of possibilities for web development, ranging from the support for feature-rich .py libraries to bi-direction communication with javascript; this makes PyScript a vital tool/skill for web development.

What Pyscript Offers

In this section, we will look at a broader range of features that PyScript provides with examples:

Browser Support For Python Scripts

PyScripts provides browser support for Python scripts without any server-side configuration. In addition, it allows you to inject Python scripts directly into your HTML interface using the py-script tag:

<html>
...
    <py-script>
    print("hello world")
    </py-script>
</html>

The code block above will print the string “hello world” in your browser:

1. "hello world" output

PyScript also allows you to inject .py in the HTML interface using a py-script tag.

<html>
...
    <py-script>
        print("hello world")
    </py-script>
    <py-script src="./hello.py">
    </py-script>
</html>

2. .py file output

Support for Python libraries

PyScript also supports popular Python libraries and packages (NumPy, panda, etc.); this allows you to leverage the features these packages provide when developing your web pages.

REPL support for Python

PyScript provides a REPL injected into the HTML interface; a REPL is an environment where users can enter evaluated inputs. In this instance, PyScript provides an environment that can evaluate python scripts on the browser using the py-repl tag.

<py-repl auto-generate="true" ></py-repl>

3. Python repl

DOM Support

PyScript also allows you to manipulate HTML DOM and elements, as seen below:

<!-- PyScript DOM Support -->
<p id="text"></p>
<py-script>
    Element("text").element.innerText = "Hello from pyscript DOM"
</py-script>

4.PyScript DOM support

Mixing Python With Javascript

PyScript allows bi-direction communication between Python and JavaScript namespaces; it enables you to use JavaScripts methods in your python script. In this article, we will demonstrate this using the localStorage method:

<html>
...
<body>
    <h1>PyScript Bi-direction Support</h1>
    <!-- PyScript Bi-Direction namespaces -->
    <py-script>
        from js import localStorage
        localStorage.setItem("text", "hello world")
    </py-script>
</body>
</html>

5. Bi-direction communication in PyScript

Setting up PyScript

In this section, we will do a walk-through of how to set up a PyScript for your projects; this is a relatively straightforward process using the CDN (Content Delivery Network) link to serve the files. You can find the CDN links for PyScript on the official PyScript website. First, click on the install button on the page and copy the PyScript framework CDN links.

6. PyScript web page

Please copy and paste the link and script tag and paste it into your HTML head tag.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- PyScript CDN links -->
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
    <title>PyScript: Memo</title>
</head>
<body>
</body>
</html>

We can see the PyScript links on line 8 and line 9.

Open Source Session Replay

OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.

replayer.png

Start enjoying your debugging experience - start using OpenReplay for free.

Sample Project Using PyScript

For this article, we will build a memo stored on your browser localStorage using PyScript and Python. The project will allow us to add, read, update and delete memos stored on the browser localStorage. To get started, create a new folder 📂store and an index.html file; repeat the setup process for setting up PyScript from the previous section. The next step will be to create the app.py ****file we will be working with and link the file in index.html.

<!DOCTYPE html>
<html lang="en">
...
<body>
    <!-- PyScrips -->
    <py-script src="./app.py">
    </py-script>
</body>
</html>

We will have the index.html file with styling as seen here: project_memo/index.html

The following process will be to code the functionalities for the app, and we will handle this in the app.py file. The features we will be covering for the project are:

  • Add New Memo
  • Read Memos
  • Update Memo
  • Delete Memo

We will first import javascript methods and initialize the global variables used throughout the project.

NB: It is vital to keep track of the indentation on the project file as this can affect code translation in .py files.

from js import document, localStorage, Object
memos = localStorage
new_memo = Element('new-memo')
add_memo_form = Element('add-memo-form')
memo_container = Element('memo-container')
update_memo = Element('update-memo')
edit_memo_form = Element('edit-memo-form')
edit_memo_container = Element('edit-memo-container')

On line 1, we import the JavaScript methods we will be working with; on lines 3-8, we use the PyScript DOM method Element. The Element method accepts the id of an HTML element.

Add New Memo

The first feature will be to add a new memo; to achieve this, we will accept a value keyed into the input with id “new-memo” and add it to the localStorage with a unique key.

def add_new_memo(e):
    e.preventDefault()
    localStorage.setItem(get_next_memo_id(), new_memo.element.value)
    new_memo.element.value = ""
    get_memos()
# Returns an integer that is the next memo id

def get_next_memo_id():
    # declares an empty python dictionary
    memo_dict = dict({})
    next_id = 0
    # function to loop through local storage and add all the key-value pairs to the python dictionary
    def memo_loop(memos_entries, _, __):
        memo_dict[memos_entries[0]] = memos_entries[1]
    Object.entries(memos).map(memo_loop)
    # Check for the max id in the dictionary and assign the value to next_id
    for memo_key in memo_dict:
        if next_id < int(memo_key):
            next_id = int(memo_key)
    return next_id + 1

...

add_memo_form.element.onsubmit = add_new_memo

On line 24 from the code block above, we have an onsubmit event on the add_memo_form element assigned add_new_memo function. The add_new_memo function on line 1 accepts an event parameter. On line 3, we save the memo with a key; We generate the memo key by calling the get_next_memo_id function. On line 10, we declare a new Python dictionary, similar to object literals in JavaScript. You can find additional information on dictionaries here. On line 13, we have the function memo_loop that loops through the memos and add the key-value pair to the dictionary memo_dict. On line 17, we loop through the memos and check for the maximum key, assign the value for the maximum key to the next_id variable, and return the next_id incremented by one as the key for a new memo.

NB: Functions in Python are declared using the def keyword.

7. add new memo

We call the get_memos function on line 5, which populates the dom with values from memos.

Read Memos

To read memos and populate the HTML DOM with the memo values, we call the get_memos;

def get_memos():
    # Clean inside the memo container lelement
    memo_container.element.innerHTML = ""
    # loop through all memo to append them to the memo container
    Object.entries(memos).forEach(memo_entries_loop)

def memo_entries_loop(memo_list, _, __):
    key = str(memo_list[0])
    memo = memos.getItem(key)
    # Creates new list element and buttons for editing and deleting memo
    btn_wrapper = document.createElement("div")
    memo_elem = document.createElement('li')
    memo_edit_btn = document.createElement(
        'button')
    memo_del_btn = document.createElement('button')
    # Set classes and id for element
    btn_wrapper.className = "flex"
    btn_wrapper.id = key
    memo_del_btn.className = "delete-btn"
    memo_edit_btn.className = "edit-btn"
    memo_elem.className = "memo-" + key
    memo_edit_btn.innerText = "Edit"
    memo_del_btn.innerText = "Delete"
    memo_elem.innerText = memo
    # Append buttons to list element
    btn_wrapper.appendChild(memo_edit_btn)
    btn_wrapper.appendChild(memo_del_btn)
    memo_elem.appendChild(btn_wrapper)
    # events
    memo_del_btn.onclick = delete_memo
    memo_edit_btn.onclick = open_edit_container
    # append the new memo to the container
    memo_container.element.appendChild(
        memo_elem)

On line 5, we loop through memos with the memo_entries_loop function; on lines 11 through 15, we create the HTML elements that will hold the memo values. Then, on line 18, we assign the key for the btn_wrapper element that appends the edit and delete button on lines 26 and 27. Finally, on line 30, we add an onclick event to the memo_del_btn button with the value delete_memo and assign the open_edit_container function to the onclick event for the memo_edit_btn button. Next, we will append the memo value to the memo_elem element on line 24 and the btn_wrapper on line 28. Following this, we append the memo_elem to the memo_container on line 33.

Update Memo

The edit button from the code block in the previous section triggers open_edit_container;

# Function to toggle the edit container
def open_edit_container(e):
    memo_id = e.target.parentNode.id
    
    edit_memo_container.element.classList.add("open")
    edit_memo_form.element.className = memo_id
    update_memo.element.value = memos.getItem(memo_id)

This function gets the memo id from the id of the parent element on line 3 and adds the “open” class to the edit_memo_container, triggering the edit modal to be visible. Next, we assign the key for the memo to edit to edit_memo_form on line 6 and assign the memo value to update_memo.

...
def edit_memo(e):
    e.preventDefault()
    memo_id = e.target.classList
    memos.setItem(memo_id, update_memo.element.value)
    close_edit_container()
    get_memos()
...
def close_edit_container():
    edit_memo_form.element.className = ""
    update_memo.element.value = ""
    edit_memo_container.element.classList.remove("open")
...
edit_memo_form.element.onsubmit = edit_memo

From the code block above, we have an onsubmit event for the edit_memo_form element that calls the edit_memo function. Then, on line 4, we assign the memo id to edit from the event target(edit_memo_form) and set the memo to the current value of the update_memo input element, call the close_edit_container function to reset the edit form and input element. Finally, we close the edit_memo_container modal by removing the ‘open’ class and repopulate the DOM with the updated memos by calling the get_memos function.

The close_edit_container is also passed to the modal’s close button using the py-onClick attribute.

<button class="delete-btn w-100" py-onClick="close_edit_container">
    Close
</button>

8. update memo

Delete Memo

To handle deleting a memo, we have the delete_memo function;

# Function to delete a memu using the key passed to parent as id
def delete_memo(e):
    memo_id = e.target.parentNode.id
    memos.removeItem(memo_id)
    get_memos()

On line 3, we get the key for the current memo to be deleted from the parent element id, remove the memo that matches the key from memos/localStorage, and call the get_memos function to update the DOM elements with the updated memos.

Conclusion

PyScript is a good framework with a lot to offer. However, I think PyScript won’t replace JavaScript for web development. You can find the complete code for the project in this GitHub Repository. To play around with the live application, visit https://pyscript-one.vercel.app/.