PyScript: Python on the Web
Tags: webdev, python, pyscript
Published: 2022-07-20 11:41:00

by [Melvin Kosisochukwu](https://blog.openreplay.com/authors/melvin-kosisochukwu)

If you have been following tech news and updates, you must have heard of [PyScript](https://pyscript.net/). 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](https://pyodide.org/en/stable/) (Pyodide makes it possible to install and run Python packages in the browser) and [WebAssembly](https://webassembly.org/); 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](https://blog.openreplay.com/static/a57498269be3ee107f1f22a1040badd6/e010c/image01.png)


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](https://blog.openreplay.com/static/a51e428bd3a187522f2d9cbfe32ca39d/e010c/image02.png)


### 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](https://blog.openreplay.com/static/097b8b371247b9984f582829135d6925/e010c/image03.png)


### 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](https://blog.openreplay.com/static/18ea265213c7fc1cc906bf481af803c1/e010c/image04.png)

### 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](https://blog.openreplay.com/static/74e64ba09a94f1aab3c880e50628a7a2/e010c/image05.png)



## 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](https://pyscript.net/). First, click on the install button on the page and copy the PyScript framework CDN links. 


![6. PyScript web page](https://blog.openreplay.com/static/e1289ec844512fe24b7ae1972826820e/1a152/image06.png)


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](https://github.com/openreplay/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](https://static.openreplay.com/images/banner-blog.png)

_Start enjoying your debugging experience - [start using OpenReplay for free](https://github.com/openreplay/openreplay)._


## 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](https://github.com/MelvinManni/pyscript/blob/main/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](https://www.w3schools.com/python/python_dictionaries.asp). 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](https://www.w3schools.com/python/python_functions.asp) are declared using the `def` keyword.

![7. add new memo](https://blog.openreplay.com/static/b0c40572a153efd2bd4e5202af7259ff/1a152/image07.png)

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](https://blog.openreplay.com/3c0b7cf58d84c5f423a3d78a1c538ef5/image08.gif)

### 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](https://github.com/MelvinManni/pyscript.git). To play around with the live application, visit [https://pyscript-one.vercel.app/](https://pyscript-one.vercel.app/).


[![newsletter](https://blog.openreplay.com/static/e6b18eda7a327511296a2f27692f9c70/e9957/newsletter.png)](https://newsletter.openreplay.com/)