Introduction
Today, you can now use Pyscript to run Python code in the web browser. This has enormous potential for AI, ML, Data Scientists, and regular Python developers. I often develop backend applications in Django and Flask, and the possibilities of writing both frontend and backend code in Python are intriguing.
This article is about how to load Python code in the web browser. There are a number of methods. Knowing these methods can improve development and debugging. I will also show methods of loading Python modules.
At this time, Pyscript is in alpha.
Including Python in the browser is amazingly simple. Include two tags in the HTML head:
1 2 |
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" /> <script defer src="https://pyscript.net/alpha/pyscript.js"></script> |
In this article I will discuss the following methods:
- Include Python with HTML
- Load Python from a file
- Load Python from multiple source files
- Load Python and execute Python after a page is displayed
- Load Python using Pyodide
HTML and JavaScript:
- Write your own Pyscript Replacement
History
It has been possible to run Python in the browser for several years (2018). Pyscript does not actually interpret Python code. Pyodide (Pie-O-Dide) is the magic behind Pyscript. In my fifth method in this article, I will show an example that does not use Pyscript and directly loads Python using JavaScript and the Pyodide library.
Understanding Pyodide is important to understanding how to use Pyscript. Pyscript’s genius is wrapping Pyodide to completely hide it for most HTML/Python applications. However, you will import some functions from Pyodide. Example: from pyodide.http import pyfetch
. I use that import to provide a replacement for requests
which is not available in Webassembly based Python (Python in the browser).
Be sure to read the last section “Write your own Pyscript”.
Method 1 – Include Python with HTML
This method uses the <py-script>
tags just like the <script>
tags. Insert your Python code between the <py-script>
and </py-script>
tags.
You must correctly format the Python code just like a normal .py file. Indentation matters.
Notice that I am wrapping the Python code in a main()
function. In a future article when I discuss async Python in the web browser, the reason will become apparent.
example1.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<!DOCTYPE html> <html> <head> <title>Example 1</title> <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" /> <script defer src="https://pyscript.net/alpha/pyscript.js"></script> </head> <body> <div id="msg">Loading page ...</div> <br> <py-script> import js from js import document def main(): msg = document.getElementById("msg") msg.innerHTML = 'Hello world' main() </py-script> </body> </html> |
This example loads in the web browser and displays “Loading page …”. Once the Python code executes, the page changes to “Hello world”. The entire process takes about two seconds. I expect this time to decrease significantly as Pyscript reaches production quality.
This example demonstrates how to get a DOM element by ID:
1 |
msg = document.getElementById("msg") |
and then write to that element:
1 |
msg.innerHTML = 'Hello world' |
Pyscript has a library named pyscript
that has built-in functions to make this easy. The following one line of code replaces the two previous lines.
1 |
pyscript.write('msg', 'Hello world') |
The source code for pyscript.write()
is here. The signature is:
1 2 |
@staticmethod def write(element_id, value, append=False, exec_id=0): |
Notice append
. If true you can append a <div>
as a child to the specified element ID.
Another interesting item is the class Element (link):
1 2 |
element = Element('msg') element.write('Hello world')') |
With those improvements, the updated example becomes:
example1.html version 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!DOCTYPE html> <html> <head> <title>Example 1</title> <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" /> <script defer src="https://pyscript.net/alpha/pyscript.js"></script> </head> <body> <div id="msg">Loading page ...</div> <br> <py-script> def main(): pyscript.write('msg', 'Hello world') main() </py-script> </body> </html> |
The key is to understand how to get DOM elements and then modify them. This is an important concept for code that runs in the browser.
Benefits:
- This example can be loaded into the web browser from a local file.
Drawbacks:
- Mixing HTML, JavaScript, and Python in the same file is hard to manage.
Method 2 – Load Python from a file
This method uses the <py-script src="filename.py">
style to load Python from a separate file. To the untrained eye, you might not even notice that Python is being loaded into the web browser.
example2.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!DOCTYPE html> <html> <head> <title>Example 2</title> <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" /> <script defer src="https://pyscript.net/alpha/pyscript.js"></script> </head> <body> <div id="msg">Loading page ...</div> <br> <py-script src="load_python_2.py"></py-script> </body> </html> |
example2.py
1 2 3 4 5 6 7 8 |
import js from js import document def main(): msg = document.getElementById("msg") msg.innerHTML = 'Hello world' main() |
This example loads the Python as a separate step similar to loading CSS or JavaScript resources. This example displays “Loading page …”. Once the Python code load and executes, the page changes to “Hello world”. The entire process takes about two seconds. I expect this time to decrease significantly as Pyscript reaches production quality.
At this time, the defer keyword is not supported. This example will not work:
1 |
<py-script defer src="example2.py"></py-script> |
As Pyscript becomes popular, this would be a nice feature to lazy load Python while the DOM is generated.
Benefits:
- Separating Python from HTML simplifies development and testing.
Drawbacks:
- This example cannot be loaded into the web browser from a local file. This method requires a web server.
Simple web server for local Pyscript development:
1 |
python -m http.server |
Method 3 – Load Python from multiple source files
Typical applications are split into multiple source files. Pyscript supports including multiple files. There is a limitation where the files cannot have the same name even if located in different directories.
1 2 3 4 5 |
<py-env> - paths: - /app/file1.py - /app/file2.py </py-env> |
Once the source files are declared, you can then import functions from those files.
1 2 3 4 5 6 7 |
<py-script> from file1 import hello from file2 import square hello() print(square(25)) </py-script> |
Method 4 – Load Python and execute Python after a page is displayed
This method uses pyfetch()
to download a file containing Python code from a web server. An important undocumented detail is that you must import the asyncio
package, otherwise, you will get unusual errors. This took me a while to figure out. Once the code is downloaded, it is executed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<!DOCTYPE html> <html> <head> <title>Example 3</title> <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" /> <script defer src="https://pyscript.net/alpha/pyscript.js"></script> </head> <body> <div id="msg">Loading page ...</div> <br> <py-script> import js from js import document from pyodide.http import pyfetch import asyncio async def load_code_from_url(url): try: msg = document.getElementById("msg") response = await pyfetch(url) content = (await response.bytes()).decode('utf-8') if response.status == 200: msg.innerHTML = 'Python loaded' return content else: msg = document.getElementById("msg") msg.innerHTML = response.status return False except Exception as e: msg = document.getElementById("msg") msg.innerHTML = str(e) return False url = 'example_2.py' mycode = await load_code_from_url(url) exec(mycode) </py-script> </body> </html> |
Benefits:
- Separating Python from HTML simplifies development and testing.
- This method permits executing Python on demand instead of when the page loads.
- Demonstrates how to use
pyfetch()
and theasyncio
package
Drawbacks:
- This example cannot be loaded into the web browser from a local file. This method requires a web server.
Method 5 – Load Python using Pyodide
Pyodide is the magic behind Pyscript. In this example, I will show how to load and execute Python without loading Pyscript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<!DOCTYPE html> <html> <head> <script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script> </head> <body> <div id="msg">Loading page ...</div> <br> <script type="text/javascript"> async function load_code_from_url(url) { let response = await fetch(url, {redirect: "follow"}); let data = await response.text(); return data; } async function main() { let pyodide = await loadPyodide(); let url = 'example_2.py'; let pycode = await load_code_from_url(url); pyodide.runPython(pycode); } main(); </script> </body> </html> |
This is a simple example of how easy it is to load a Python source file from a web server and run that Python code in the web browser.
Importing Python Packages
Pyscript defines the tag <py-env>
which defines the Python packages your program requires.
Example:
1 2 3 4 5 6 7 8 9 |
<body> <py-env> - numpy </py-env> <py-script> ... </py-script> </body> |
You can also declare the package version:
1 2 3 |
<py-env> - numpy==1.22.3 </py-env> |
In some cases, you do not need to use <py-env>
. I have not figured out yet when using <py-env>
is required. My examples above, do not use <py-env>
, they just import the packages from the Python code. This might be because the packages are already part of the Pyscript downloads. numpy
is an example that requires declaration in <py-env>
. Pyscript is brand new and my knowledge is limited but I am digging into everything.
Read Method 3 – Load Python from multiple source files for details on how to include multiple source files.
Write your own Pyscript Replacement
If you want to surprise a few developers, try this example. Here I show you how to create your own DOM tag element using, for example, your name. In this example, I use jhanley-python
to signify the Python code to execute:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<!DOCTYPE html> <html> <head> <script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script> </head> <body> <div id="msg">Loading page ...</div> <br> <jhanley-python> import js from js import document def main(): msg = document.getElementById("msg") msg.innerHTML = 'Hello world' main() </jhanley-python> <script type="text/javascript"> async function load_and_run_python_script() { var tag = document.getElementsByTagName('jhanley-python'); // Hide the tag contents tag[0].style.display = "none"; let pycode = tag[0].innerHTML; let pyodide = await loadPyodide(); pyodide.runPython(pycode); } load_and_run_python_script(); </script> </body> </html> |
If you study that example, you will see how easy it is to implement Python in the browser using Pyodide. Pyscript offers many more features than my simpleton example, but with this, you might be able to contribute to Pyscript by knowing how some things are done.
Move the Javascript to a separate file and few would notice how this is done.
Pyscript Issue
Pyscript has one issue that my example could be used to fix. For a brief period of time, the Python code is visible in the browser window. This can be fixed by using Javascript and my section that hides a custom DOM element tag. For example:
1 2 3 |
var tag = document.getElementsByTagName('py-script'); // Hide the tag contents tag[0].style.display = "none"; |
This code needs to run before the Pyscript libraries start loading.
Summary
I am very intrigued with Pyscript. Writing frontend and backend applications in Python can be very useful. Today, the browser is often used as a display device. I want to use the browser to distribute processing power and move some activity from the server to the browser.
A simple example is uploading a large file for processing such as a spreadsheet. Now the spreadsheet can be processed and displayed in the browser, while only uploading the required rows and columns to the backend for storage. This saves a roundtrip where large amounts of data are transferred over the network.
One can argue that JavaScript does that today. That is true, but there are applications that are very difficult or impossible with JavaScript that is a walk in the park for Python.
The spreadsheet is a simplification of a common problem, but take this to the next step, which is AI, ML, and Data Science. These are areas where Python blows JavaScript away. Importing libraries such as numpy
have enormous potential.
More Information
- Other articles that I have written on Pyscript
- Pyscript
- Anaconda – PyScript: Python in the Browser
- Pyodide
- Pyscript Examples
Photography Credits
Heidi Mustonen just started a new photography company in Seattle, WA. Her company in-TENSE Photography has some amazing pictures. I asked her for some images to include with my new articles. Check out her new website.
I design software for enterprise-class systems and data centers. My background is 30+ years in storage (SCSI, FC, iSCSI, disk arrays, imaging) virtualization. 20+ years in identity, security, and forensics.
For the past 14+ years, I have been working in the cloud (AWS, Azure, Google, Alibaba, IBM, Oracle) designing hybrid and multi-cloud software solutions. I am an MVP/GDE with several.
June 3, 2022 at 9:55 AM
Nice article ! Thanks for this sharing. I’m exiting to read the other one.