Run client-side python in your browser to prezent your code.
GitHub Project (go to project)
GitHub Source (inspect code)
GitHubPages (interactive README)
Contact(I'd love to hear feedback on ideas on how to make this better!)
<script src="https://modularizer.github.io/pyprez/pyprez.min.js"> print("2^4=",2**4) </script>- StackOverflow
- About
- Getting Started
- Use Cases
- Limitations
- Micropip
- Tags:
<pyprez-editor>,<pyprez-console>,<pyprez-import>,<pyprez-script> - Custom Themes
- Feature Development
- Keyboard Shortcuts
- API
- Pyodide
- PyScript
- Samples
Using any <prprez-editor> element (such as the one here) to modify your code, then click M↓ button on the top bar to convert your python into markdown which can be pasted into your StackOverflow answer to create a runnable and editable snippet.
The Question/Answer Editor will look something like this 
Use this Converter Page
Add a bookmark to your browser with the following text as the url.
javascript:(()=>{ let sel=window.getSelection().toString().split("\n").join("\n "); if (sel.startsWith('```python')){sel = sel.replace('```python', '').slice(0,-3)}; navigator.clipboard.writeText(` \x3C!-- begin snippet: js hide: false console: false babel: false --> \x3C!-- language: lang-js --> #!/usr/bin/env python\n ${sel} \x3C!-- language: lang-html --> \x3Cscript src="https://modularizer.github.io/pyprez/pyprez.min.js" theme="darcula">\x3C/script> \x3C!-- end snippet --> `)})() Ctrl + Dto save bookmark of this page (for Chrome)- Right click on existing bookmark of this page, click edit, then replace bookmark url with the text copied from above
- on any webpage highlight the python codeblock you want to use and click the bookmark to copy markdown to clipboard
- paste the markdown into your stackoverflow answer
- disable console logging
- paste the following into HTML or click
Add an external libraryjust the url<script src="https://modularizer.github.io/pyprez/pyprez.min.js"></script> - write your python in the javascript box
- make sure to add a comment or an import to the top of the python script (
# pythonworks). This will throw a javascript error and ensure the code runs only as python
PySnippet is a super concise package which also allows you to demo python code on stack overflow.
pyprez is a minimal javascript package which allows you to present runnable python samples in the browser.
The functionality comes primarily from Pyodide, which allows you to run front-end Python through WebAssembly and easily interact between Python, javascript and HTML. The pyodide object is made available at window.pyodide. Meanwhile much of the visual style is provided by CodeMirror (accessible at window.CodeMirror).
pyprez is inspired Pyscript, a project backed by Anaconda which provided a useful interface for pyodide also but introduced a list of drawbacks in the process.
Double-Click or press Green Arrow to run code
<script src="https://modularizer.github.io/pyprez/pyprez.js" mode="editor"> import numpy as np print("testing") np.random.rand(5) </script>NOTE: If this is the first time using pyodide on your device, it will take extra long to load (especially on mobile)
Options:
mode="editor"(default),"console","script","import"stdout="true"(default)(logs to textbox), or "false"(logs to console)
Some markdown flavors, github included, disable javascript, so these examples will not work on GitHub. Luckily, they work on GitHub Pages, so if you click the static image it will take you to the working example.
Some cool things about pyodide which pyprez takes advantage of are:
- it runs a real python interpreter
- a webpage visitor running python uses no server-side computational resources after loading the page
- no server is needed! it will work if you open a
.htmldocument in your browser - code execution is sandboxed in the webpage visitor's browsers, making it relatively safe for a webpage host to allow users to write and run their own code (because it runs on the client's machine it doesn't pose a security risk to the server)
- allows combining python computations with pretty
HTML/JS/CSS
I don't expect front-end python to replace back-end python or front-end javascript, but it does have some unique advantages for certain use cases:
- troubleshooting code on forums such as stackoverflow
StackOverflow
- making Python tutorials (similar to
jupyterofCoLab) - writing articles about new Python features
- providing interactive documentation for a package
- distributing results of scientific studies and allowing users to play around with data (this is what Pyodide was made for)
Unfortunately, there are currently many limitations of running Python in the browser, which stem from fundamental issues. Many of PyPrez's limitations stem from limitations of Pyodide, the package on which it is built. Pyodide's limitations which in turn stem from limitiations of js, Emscriptem, WebAssembly, and browsers in general. Some such limitations are:
- many packages are not supported
time.sleepis not supportedthreadingis not supported- cannot access the local file system ( but can still read and write temporary files in webassembly)
__builtins__.inputis tricky. Currently it works with the fully blockingwindow.promptfunction
The micropip object from pyodide is available at window.pyodide. Furthermore, <pyprez-editor> will autorecognize comments like # micropip install package_name and call micropip.install('package_name')before running your code.
The <pyprez-editor> tag provides a CodeMirror text editor element and does not execute until it is double-clicked (useful on mobile) or the green start button has been pressed. Then, the editor runs the code in the browser, streaming STDOUT and STDERR to either the text box or the console if stdout="false" is set, and the displays the result as a string in the editor. Additionally, the element can be reset and the code can be modified and rerun.
By default, the <pyprez-editor> tag evaluates Python in pyodide's CPython interpreter, but if the language attribute is set to "javascript" or if the src address ends with .js, the editor will run the code in javascript instead.
<pyprez-editor> import numpy as np np.random.rand(5) </pyprez-editor>pyprez.loadAndRunAsync(` from js import alert alert('pyodide object has loaded and is available at window.pyodide') `)The <pyprez-console> tag provides a minimal terminal emulator to play around with pyodide. It does the very basics and nothing more (no special color strings, no plots, etc.). It can be styled, but that is about it.
Pyodide's own console has much more support.
<pyprez-console></pyprez-console><pyprez-console rows="10" cols="80"></pyprez-console>The <pyprez-import> tag allows you to load libraries using pyodide.loadPackage function. Accepted inputs are either innerHTML or a src attribute linking to a file like a requirements.txt. This tag is not totally necessary because the pyprez.loadAndRunAsync function handles loading package dependencies via pyodide.loadPackageFromImports.
The package names are selected from the text using the regular expression /\s*-?\s*(.?)\s[==[0-9|.]]?\s[,|;|\n]/g
note: the ==version syntax used by pip freeze is ignored by the RegExp above, so specifying versions will not cause an error, but will not actually load that particular version, because this is not supported by pyodide
<pyprez-import> - numpy - datetime </pyprez-import> <pyprez-import src="./requirements.txt"></pyprez-import>The <pyprez-script> tag allows you to run Python code using pyprez.loadAndRunAsync, which uses pyodide.loadPackageFromImports followed by pyodide.runPythonAsync. Accepted inputs are either innerHTML or a src attribute linking to a python file.
<pyprez-script id="testScript"> from js import document import datetime el = document.getElementById("testScript") el.style.display = "block" el.innerText = str(datetime.datetime.now().isoformat()) </pyprez-script>set the theme attribute of the script import element or pyprez-editor element to use a special CodeMirror theme, e.g. theme="darcula". You can also select from a few themes using the dropdown.
If theme is not specified on an element, the page will use localStorage to identify the last saved preferred theme of the client! Unfortunately on StackOverflow the code snippets are isolated in such a way that this does not work :/
see available themes at https://codemirror.net/5/demo/theme.html
<pyprez-editor theme="darcula"> import numpy as np np.random.rand(5) </pyprez-editor>If you have multiple pyprez-editor elements on the same page, namespaces let you set which ones should share variable scopes and which ones should not. Simply set the namespace attribute on elements, and if you wish to change them live, set the showNamespaceSelect attribute on the script which imports pyprez.js. Namespace Demo
- import other namespaces
- relative imports to load other python files based on the
src
The input function is tricky. For now, we have gotten it to call the builtin js prompt popup.
- imitate the typical inline input
We have applied a patch which runs only if matplotlib is imported. It overwrites the plt.plot function and the Figure.savefig to save the figure to a temporary png, convert to a base64 dataURI, and set that as the src of an img to display the figure.
Making it interactive would be tough (but not impossible). Bokeh and Plotly are supported and may be the easier route.
- interactivity (TBD whether or not this will happen)
<pyprez-editor> import matplotlib.pyplot as plt import numpy as np p = int(input("how many points?")) fig = plt.figure() plt.plot(np.random.rand(p)) </pyprez-editor>Ctrl+k can be used to auto-format code to pep8 standards. Ctrl+Z will undo.
custom:
Ctrl + k: autopep8.fix_fileShift + Enter: run codeShift + Backspace: reset code
builtin to codemirror:
Ctrl + zCtrl + aCtrl + c
Any html elements created by the pyprez custom tags get added to pyprez.elements object for easy retrieval. Further, <pyprez-editor> elements can be accessed from pyprez.editors, <pyprez-console> from pyprez.consoles, etc.
When pyprez.js loads, the pyprez object (available at window.pyprez) creates a Promise at pyprez.promise, which then resolves with true when loadPyodide finishes loading the pyodide object.
pyprez.then and pyprez.catch are simply shortcuts to pyodidePromise.then and pyodidePromise.catch. Therefore, pyprez.then can be use be sure that pyodide has finished loading, then use it as soon as possible.
pyprez.then(() => window.pyodide.runPythonAsync(` from js import alert alert("pyodide object has loaded and is available at window.pyodide") `))The pyprez.loadAndRunAsync function is an asynchronous utility function which immediately returns a Promise to the result of some Python code, which gets evaluated as soon as possible. It works by doing does three things:
- waits for pyodide to finish loading by using
pyprez.then - loads any packages the code snippet requires, by using
pyodide.loadPackagesFromImports - runs python in pyodide's CPython interpreter using WebAssembly via
pyodide.runPythonAsync
pyprez.loadAndRunAsync(` from js import alert alert("pyodide object has loaded and is available at window.pyodide") `)Pyprez automatically set up stdout to be handled by console.log and stderr to be handled by console.err by setting configuration options in loadPyodide. However, pyprez.stdout and pyprez.stderr functions can be set to whatever handler you want.
function appendText(m, color="#000"){ let el = document.createElement("div") el.innerText = m el.style.color = color document.getElementById("stdouttarget").append(el) } pyprez.stdout = appendText pyprez.stderr = m => appendText(m, "red") pyprez.loadAndRunAsync(` for i in range(10): print(i) raise Exception("testing stderr") `)Pyodide is a super cool project which uses Empscripten to compile and run a CPython interpreter in the browser using WebAssembly.
pyodide provides access toWebAPIs ( such as window, document, etc. ) and all of your javascript objects, functions, etc. from Python and also allows accessing and setting python variables from javascript. In reality, Pyodide provides ~99.9% of the utility of Pyprez, which just provides a user interface.
Pyodide is a great foundation with cool features, great documentation and lots of potential use cases mostly related to:
- offloading computations to browsers to reduce server resources
- speeding up slow client-side computations (especially ones which can be vectorized)
- distributing research and data analysis documents (this was the goal of the now-deprecated Iodide Project from which Pyodide originated)
- allowing Python developers to dabble in web development a bit easier
Pyodide is not yet fully fledged, and is still working on features such as threading, calling blocking functions such as time.sleep, and allowing using multiple web workers to run code.
Pyprez is heavily inspired by PyScript, a project recently endorsed by Anaconda (May 2022), which is built on top of Pyodide and attempts to make Pyodide easier to use by providing custom HTML tags such as py-env, py-script and py-repl and by allowing users to easily display plots and graphs using matplotlib and other similar popular Python Libraries.
I believe that PyScript may eventually become the state of the art for Python in the browser. For now though, they have set their sights too high and failed to deliver. As of May 2022, there were many critical issues:
- very slow load times of (10-30s)
- poor documentation
- Their own Hello World example for PyScript did not work
- the
pyodideobject which Pyscript is based off of is not easily provided to the user as awindowvariable,loadPyodide()does not allow reloading of thepyodideobject, and no documented interface topyodideis provided, meaning the user loses out on most of pyodide's javascript API and versatility
PyScript seems to be so focused on making web development "accessible" to Python developers, that they ended up removing most of the Pyodide functionality developers are looking for and instead made a slow, bulky, buggy, front-end version of a Jupyter notebook.
PyScript has improved and will continue to get better, and I look forward to a day when it is simple to use, well documented, and easy to extend. For now though, I hope you enjoy this alternative!
Scripts which make GitHub Pages page interactive
<script> let samples = Array.from(document.querySelectorAll('.nonrendered')); samples.map(el=>{el.style.display="none"}); let fav = document.createElement("link"); fav.setAttribute("rel","icon"); fav.setAttribute("type","image/x-icon"); fav.setAttribute("href", "https://modularizer.github.io/pyprez/favicon.ico"); document.head.appendChild(fav); let scripts = Array.from(document.querySelectorAll('.language-html')); let i = 0; scripts.map(el=>{ let parent = document.createElement("div"); if (i>1){ parent.innerHTML = el.innerText.replaceAll("\x3C","<"); }else if (i==1){ parent.innerHTML = `<pyprez-editor mode="editor"> import numpy as np print("testing") np.random.rand(5) </pyprez-editor>` }else if (i==0){ el.outerHTML = `<pyprez-editor mode="editor"> print("2^4=",2**4) </pyprez-editor>` } el.after(parent); i += 1; }) let jscripts = Array.from(document.querySelectorAll('.language-javascript')); jscripts.map(el=>{ let parent = document.createElement("div"); let content = el.innerText; parent.innerHTML = `<pyprez-editor language="js">${content}</pyprez-editor>` console.log(parent) el.after(parent); }) </script> 


