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 + D
to 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 library
just 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 (
# python
works). 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
.html
document 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
jupyter
ofCoLab
) - 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.sleep
is not supportedthreading
is not supported- cannot access the local file system ( but can still read and write temporary files in webassembly)
__builtins__.input
is tricky. Currently it works with the fully blockingwindow.prompt
function
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 + z
Ctrl + a
Ctrl + 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
pyodide
object which Pyscript is based off of is not easily provided to the user as awindow
variable,loadPyodide()
does not allow reloading of thepyodide
object, and no documented interface topyodide
is 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>