Commit 7441d525 authored by Michael Droettboom's avatar Michael Droettboom

Partially address #338: Provide simple method for interactive testing

This adds a basic repl-like console webpage that can be used for
testing of the local pyodide install.

It removes the broken examples based on standalone iodide.
parent 89eca5f5
......@@ -50,10 +50,7 @@ all: build/pyodide.asm.js \
build/pyodide.asm.data \
build/pyodide.js \
build/pyodide_dev.js \
build/python.html \
build/python_dev.html \
build/matplotlib.html \
build/matplotlib-sideload.html \
build/console.html \
build/renderedhtml.css \
build/test.data \
build/packages.json \
......@@ -91,23 +88,11 @@ build/pyodide.js: src/pyodide.js
sed -i -e "s#{{ABI}}#$(PYODIDE_PACKAGE_ABI)#g" $@
build/python.html: src/python.html
cp $< $@
build/python_dev.html: src/python_dev.html
cp $< $@
build/matplotlib.html: src/matplotlib.html
cp $< $@
build/matplotlib-sideload.html: src/matplotlib-sideload.html
build/test.html: src/test.html
cp $< $@
build/test.html: src/test.html
build/console.html: src/console.html
cp $< $@
......
......@@ -66,24 +66,27 @@ on your `PATH`.
# Manual Testing
The port 8000 of the docker environment and the host system are automatically
The port 8000 of the docker environment and the host system are automatically
binded when ``./run_docker`` is run.
This can be used to test the ``pyodide`` builds running within the docker
This can be used to test the ``pyodide`` builds running within the docker
environment using external browser programs on the host system.
To do this, simply run ``./bin/pyodide serve``
This serves the ``build`` directory of the ``pyodide`` project on port 8000.
* To serve a different directory, use the ``--build_dir`` argument followed by
* To serve a different directory, use the ``--build_dir`` argument followed by
the path of the directory
* To serve on a different port, use the ``--port`` argument followed by the
* To serve on a different port, use the ``--port`` argument followed by the
desired port number
Make sure that the port passed in ``--port`` argument is same as the one
Make sure that the port passed in ``--port`` argument is same as the one
defined as ``DOCKER_PORT`` in the ``run_docker`` script.
Once the webserver is running, for simple interactive testing, visit the URL
[http://localhost:8000/console.html](http://localhost:8000/console.html)
# Benchmarking
Install the same dependencies as for testing.
......
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://code.jquery.com/jquery-latest.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery.terminal/js/jquery.terminal.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/jquery.terminal/css/jquery.terminal.min.css" rel="stylesheet"/>
<script src="./pyodide_dev.js"></script>
</head>
<body>
<script>
languagePluginLoader.then(() => {
pyodide.runPython('import io, code, sys');
pyodide.runPython('_c = code.InteractiveConsole(locals=globals())')
var c = pyodide.pyimport('_c')
function handleResult(result) {
if (result) {
term.set_prompt('[[;gray;]... ]')
} else {
term.set_prompt('[[;red;]>>> ]')
}
var stderr = pyodide.runPython("sys.stderr.getvalue()").trim()
if (stderr) {
term.echo(`[[;red;]${stderr}]`)
} else {
var stdout = pyodide.runPython("sys.stdout.getvalue()")
term.echo(stdout.trim())
}
}
function pushCode(line) {
pyodide.runPython('sys.stdout = io.StringIO()')
pyodide.runPython('sys.stderr = io.StringIO()')
if (line.startsWith('import ')) {
pyodide.runPythonAsync(line).then(handleResult)
} else {
handleResult(c.push(line))
}
}
var term = $('body').terminal(
pushCode,
{
greetings: "Welcome to the Pyodide terminal emulator 🐍",
prompt: "[[;red;]>>> ]"
}
);
});
</script>
</body>
</html>
......@@ -2,45 +2,9 @@
<html>
<head>
<meta charset="UTF-8">
<title>Python - iodide</title>
<link rel="stylesheet" type="text/css" href="https://iodide.io/dist/iodide.pyodide-20180623.css">
<title>Loading matplotlib example...</title>
</head>
<body>
<script id="jsmd" type="text/jsmd">
%% meta
{
"title": "Python",
"languages": {
"js": {
"pluginType": "language",
"languageId": "js",
"displayName": "Javascript",
"codeMirrorMode": "javascript",
"module": "window",
"evaluator": "eval",
"keybinding": "j",
"url": ""
},
"py": {
"languageId": "py",
"displayName": "python",
"codeMirrorMode": "python",
"keybinding": "p",
"url": "https://iodide.io/pyodide-demo/pyodide.js",
"module": "pyodide",
"evaluator": "runPython",
"pluginType": "language"
}
},
"lastExport": "2018-05-04T17:13:00.489Z"
}
%% md
Press Shift+Enter on the cell below to display the plot.
%% code {"language":"py"}
</script>
<script>
var blackout = document.createElement('div');
blackout.setAttribute('style', 'width: 100%; height: 100%; position: absolute; left: 0; top: 0; background-color: #00000066; z-index: 10');
......@@ -48,24 +12,20 @@ Press Shift+Enter on the cell below to display the plot.
var url_string = window.location;
var url = new URL(url_string);
var content = url.searchParams.get("sideload");
var content = url.searchParams.get("example");
fetch(content).then((response) => response.text())
.then((text) => {
let jsmd = document.getElementById('jsmd');
jsmd.innerHTML = jsmd.innerHTML + text;
let script = document.createElement('script');
script.src = 'https://iodide.io/dist/iodide.pyodide-20180623.js';
script.onload = () => {
let pyodide = document.createElement('script');
pyodide.src = 'https://iodide.io/pyodide-demo/pyodide.js';
pyodide.onload = () => {
languagePluginLoader.then(() => {
window.pyodide.loadPackage('matplotlib').then(() => {
window.pyodide.runPython('__name__ = "__main__"');
blackout.parentNode.removeChild(blackout);
});
let pyodide = document.createElement('script');
pyodide.src = 'pyodide_dev.js';
pyodide.onload = () => {
languagePluginLoader.then(() => {
window.pyodide.loadPackage('matplotlib').then(() => {
window.pyodide.runPython('__name__ = "__main__"');
blackout.parentNode.removeChild(blackout);
});
};
});
document.body.appendChild(pyodide);
}
document.body.appendChild(script);
......
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Python - iodide</title>
<link rel="stylesheet" type="text/css" href="https://iodide.io/dist/iodide.pyodide-20180623.css">
</head>
<body>
<script id="jsmd" type="text/jsmd">
%% meta
{
"title": "Python",
"languages": {
"js": {
"pluginType": "language",
"languageId": "js",
"displayName": "Javascript",
"codeMirrorMode": "javascript",
"module": "window",
"evaluator": "eval",
"keybinding": "j",
"url": ""
},
"py": {
"languageId": "py",
"displayName": "python",
"codeMirrorMode": "python",
"keybinding": "p",
"url": "https://iodide.io/pyodide-demo/pyodide.js",
"module": "pyodide",
"evaluator": "runPython",
"pluginType": "language"
}
},
"lastExport": "2018-05-04T17:13:00.489Z"
}
%% plugin
{
"languageId": "py",
"displayName": "python",
"codeMirrorMode": "python",
"keybinding": "p",
"url": "https://iodide.io/pyodide-demo/pyodide.js",
"module": "pyodide",
"evaluator": "runPython",
"pluginType": "language"
}
%% js
pyodide.loadPackage('matplotlib')
%% code {"language":"py"}
from matplotlib import pyplot as plt
plt.plot([1,2,3])
plt.show()
%% code {"language":"py"}
import matplotlib.cm as cm
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, PathPatch
from matplotlib.path import Path
from matplotlib.transforms import Affine2D
import numpy as np
r = np.random.rand(50)
t = np.random.rand(50) * np.pi * 2.0
x = r * np.cos(t)
y = r * np.sin(t)
fig, ax = plt.subplots(figsize=(6, 6))
circle = Circle((0, 0), 1, facecolor='none',
edgecolor=(0, 0.8, 0.8), linewidth=3, alpha=0.5)
ax.add_patch(circle)
im = plt.imshow(np.random.random((100, 100)),
origin='lower', cmap=cm.winter,
interpolation='spline36',
extent=([-1, 1, -1, 1]))
im.set_clip_path(circle)
plt.plot(x, y, 'o', color=(0.9, 0.9, 1.0), alpha=0.8)
# Dolphin from OpenClipart library by Andy Fitzsimon
# <cc:License rdf:about="http://web.resource.org/cc/PublicDomain">
# <cc:permits rdf:resource="http://web.resource.org/cc/Reproduction"/>
# <cc:permits rdf:resource="http://web.resource.org/cc/Distribution"/>
# <cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/>
# </cc:License>
dolphin = """
M -0.59739425,160.18173 C -0.62740401,160.18885 -0.57867129,160.11183
-0.57867129,160.11183 C -0.57867129,160.11183 -0.5438361,159.89315
-0.39514638,159.81496 C -0.24645668,159.73678 -0.18316813,159.71981
-0.18316813,159.71981 C -0.18316813,159.71981 -0.10322971,159.58124
-0.057804323,159.58725 C -0.029723983,159.58913 -0.061841603,159.60356
-0.071265813,159.62815 C -0.080250183,159.65325 -0.082918513,159.70554
-0.061841203,159.71248 C -0.040763903,159.7194 -0.0066711426,159.71091
0.077336307,159.73612 C 0.16879567,159.76377 0.28380306,159.86448
0.31516668,159.91533 C 0.3465303,159.96618 0.5011127,160.1771
0.5011127,160.1771 C 0.63668998,160.19238 0.67763022,160.31259
0.66556395,160.32668 C 0.65339985,160.34212 0.66350443,160.33642
0.64907098,160.33088 C 0.63463742,160.32533 0.61309688,160.297
0.5789627,160.29339 C 0.54348657,160.28968 0.52329693,160.27674
0.50728856,160.27737 C 0.49060916,160.27795 0.48965803,160.31565
0.46114204,160.33673 C 0.43329696,160.35786 0.4570711,160.39871
0.43309565,160.40685 C 0.4105108,160.41442 0.39416631,160.33027
0.3954995,160.2935 C 0.39683269,160.25672 0.43807996,160.21522
0.44567915,160.19734 C 0.45327833,160.17946 0.27946869,159.9424
-0.061852613,159.99845 C -0.083965233,160.0427 -0.26176109,160.06683
-0.26176109,160.06683 C -0.30127962,160.07028 -0.21167141,160.09731
-0.24649368,160.1011 C -0.32642366,160.11569 -0.34521187,160.06895
-0.40622293,160.0819 C -0.467234,160.09485 -0.56738444,160.17461
-0.59739425,160.18173
"""
vertices = []
codes = []
parts = dolphin.split()
i = 0
code_map = {
'M': (Path.MOVETO, 1),
'C': (Path.CURVE4, 3),
'L': (Path.LINETO, 1)
}
while i < len(parts):
code = parts[i]
path_code, npoints = code_map[code]
codes.extend([path_code] * npoints)
vertices.extend([[float(x) for x in y.split(',')] for y in
parts[i + 1:i + npoints + 1]])
i += npoints + 1
vertices = np.array(vertices, np.float)
vertices[:, 1] -= 160
dolphin_path = Path(vertices, codes)
dolphin_patch = PathPatch(dolphin_path, facecolor=(0.6, 0.6, 0.6),
edgecolor=(0.0, 0.0, 0.0))
ax.add_patch(dolphin_patch)
vertices = Affine2D().rotate_deg(60).transform(vertices)
dolphin_path2 = Path(vertices, codes)
dolphin_patch2 = PathPatch(dolphin_path2, facecolor=(0.5, 0.5, 0.5),
edgecolor=(0.0, 0.0, 0.0))
ax.add_patch(dolphin_patch2)
plt.show()
%% code {"language":"py"}
import matplotlib.pyplot as plt
from matplotlib.tri import Triangulation
from matplotlib.patches import Polygon
import numpy as np
def update_polygon(tri):
if tri == -1:
points = [0, 0, 0]
else:
points = triang.triangles[tri]
xs = triang.x[points]
ys = triang.y[points]
polygon.set_xy(np.column_stack([xs, ys]))
def motion_notify(event):
if event.inaxes is None:
tri = -1
else:
tri = trifinder(event.xdata, event.ydata)
update_polygon(tri)
plt.title('In triangle %i' % tri)
event.canvas.draw()
# Create a Triangulation.
n_angles = 16
n_radii = 5
min_radius = 0.25
radii = np.linspace(min_radius, 0.95, n_radii)
angles = np.linspace(0, 2 * np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
angles[:, 1::2] += np.pi / n_angles
x = (radii*np.cos(angles)).flatten()
y = (radii*np.sin(angles)).flatten()
triang = Triangulation(x, y)
triang.set_mask(np.hypot(x[triang.triangles].mean(axis=1),
y[triang.triangles].mean(axis=1))
< min_radius)
# Use the triangulation's default TriFinder object.
trifinder = triang.get_trifinder()
# Setup plot and callbacks.
plt.subplot(111, aspect='equal')
plt.triplot(triang, 'bo-')
polygon = Polygon([[0, 0], [0, 0]], facecolor='y') # dummy data for xs,ys
update_polygon(-1)
plt.gca().add_patch(polygon)
plt.gcf().canvas.mpl_connect('motion_notify_event', motion_notify)
plt.show()
</script>
<div id='page'></div>
<script src='https://iodide.io/dist/iodide.pyodide-20180623.js'></script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Python - iodide</title>
<link rel="stylesheet" type="text/css" href="https://iodide.io/dist/iodide.pyodide-20180623.css">
</head>
<body>
<script id="jsmd" type="text/jsmd">
%% meta
{
"title": "Python",
"languages": {
"js": {
"pluginType": "language",
"languageId": "js",
"displayName": "Javascript",
"codeMirrorMode": "javascript",
"module": "window",
"evaluator": "eval",
"keybinding": "j",
"url": ""
},
"py": {
"languageId": "py",
"displayName": "python",
"codeMirrorMode": "python",
"keybinding": "p",
"url": "https://iodide.io/pyodide-demo/pyodide.js",
"module": "pyodide",
"evaluator": "runPython",
"pluginType": "language"
}
},
"lastExport": "2018-05-04T17:13:00.489Z"
}
%% md
# Pyodide 🐍
Pyodide adds support for Python in an Iodide notebook, running inside your browser.
**This is early days. Everything here is subject to change.**
(A major shortcoming is that `print` from Python currently prints to the Javascript debugger console, rather than to the notebook cell, so some of these examples are more contrived than they need to be.)
**Also to note: If you have any issues, try disabling any ad or tracking blockers for this site.**
First, let's use a plugin cell to load the Python interpreter and tell Iodide about the new cell type.
%% plugin
{
"languageId": "py",
"displayName": "python",
"codeMirrorMode": "python",
"keybinding": "p",
"url": "https://iodide.io/pyodide-demo/pyodide.js",
"module": "pyodide",
"evaluator": "runPython",
"pluginType": "language"
}
%% md
## Make a Python cell. Import stuff and use it.
Most of the standard library (at least the parts that make sense) are here and available to use.
%% code {"language":"py"}
# python
import sys
sys.version
%% md
## Basic data types
The basic data types (None, bool, ints, floats, lists, and dicts) are converted from Python to Javascript when they are output and displayed using the standard mechanisms in Iodide.
%% code {"language":"py"}
[0, 1, 32.0, 'foo', {'a': 10, 'b': '20'}, b'bytes']
%% md
## Sharing objects between Python and Javascript
The Python and Javascript sides can pass objects back and forth.
So, you can set a value in Javascript code:
%% js
// javascript
secret = "Wklv#lv#olnh#pdjlf$"
%% md
...and use it from Python by using `from js import ...`:
%% code {"language":"py"}
# python
from js import secret
decoded = ''.join(chr(ord(x) - 3) for x in secret)
%% md
...and then get it back from Javascript using `pyodide.pyimport`:
%% js
// javascript
var decoded = pyodide.pyimport("decoded")
decoded
%% md
## Custom data types
Non-basic data types, such as class instances, functions, File objects etc., can also be passed between Python and Javascript.
### Using Python objects from Javascript
For example, say we had the following Python function that we wanted to call from Javascript:
%% code {"language":"py"}
# python
def square(x):
return x * x
%% md
Since calling conventions are a bit different in Python than in Javascript, all Python callables take two arguments when called from Javascript: the positional arguments as an array, and the keyword arguments as an object.
%% js
// javascript
var square = pyodide.pyimport("square")
square(2.5)
%% md
This is equivalent to the following Python syntax:
%% code {"language":"py"}
# python
square(2.5)
%% md
You can also get the attributes of objects in a similar way. Say we had an instance of the following Python custom class:
%% code {"language":"py"}
# python
class Foo:
def __init__(self, val):
self.val = val
foo = Foo(42)
foo
%% md
We can get the value of its `val` property as so:
%% js
// javascript
var foo = pyodide.pyimport("foo")
foo.val
%% md
### Using Javascript objects from Python
Likewise, you can use Javascript objects from Python.
%% js
// javascript
function cube(x) {
return x*x*x;
}
%% md
To call this function from Python...
%% code {"language":"py"}
from js import cube
cube(4)
%% md
## Exceptions
Python exceptions are converted to Javascript exceptions, and they include tracebacks.
%% code {"language":"py"}
x = 5 / 0
%% md
## World DOMination
By using `from js import document`, you can easily access the Web API from Python.
For example, get the title of the document:
%% code {"language":"py"}
# python
from js import document
document.title
%% md
You can set it, too:
%% code {"language":"py"}
# python
document.title = 'My mind is blown'
%% md
We can set up a special `div` element from a markdown cell, and then manipulate it from Python.
<div id="targetDiv">This is a div we'll target from Python</div>
%% code {"language":"py"}
# python
# Turn the div red
document.getElementById("targetDiv").setAttribute("style", "background-color: red")
%% md
## Numpy
You bet, [Numpy](http://numpy.org) works.
To save on download times, isn't loaded by default. We need to manually use
the `pyodide.loadPackage` function from a Javascript cell.
%% js
pyodide.loadPackage('numpy')
%% md
Now that the Numpy package has been loaded (i.e. transferred to your local browser), we can import it:
%% code {"language":"py"}
import numpy as np
%% md
Let's make a simple array of zeros. When it's displayed, it's using the same output code that Iodide uses for Javascript.
(On a technical level, it's important to note that Pyodide doesn't need to copy the whole array over to the Javascript side to do this: it's only accessing the parts of the array it needs to make the display.)
%% code {"language":"py"}
np.zeros((16, 16))
%% md
### Estimating pi
Here's a fun example where we can estimate pi by generating a bunch of random (x, y) points and calculating the ratio of them that fall within the unit circle.
%% code {"language":"py"}
from numpy import random
points = (random.rand(1000, 2) * 2.0) - 1.0
%% code {"language":"py"}
x = points[:, 0]
y = points[:, 1]
inside_circle = (x*x + y*y) < 1.0
pi = (float(np.sum(inside_circle)) / float(len(points))) * 4.0
pi
%% md
## Coming soon..
A couple things that already work that will be coming to this example notebook soon...
- Pandas support
- Plotting using D3 from Python
%% js
</script>
<div id='page'></div>
<script src='https://iodide.io/dist/iodide.pyodide-20180623.js'></script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Python - iodide</title>
<link rel="stylesheet" type="text/css" href="https://iodide.io/dist/iodide.pyodide-20180623.css">
</head>
<body>
<script id="jsmd" type="text/jsmd">
%% meta
{
"title": "Python",
"languages": {
"js": {
"pluginType": "language",
"languageId": "js",
"displayName": "Javascript",
"codeMirrorMode": "javascript",
"module": "window",
"evaluator": "eval",
"keybinding": "j",
"url": ""
},
"py": {
"languageId": "py",
"displayName": "python",
"codeMirrorMode": "python",
"keybinding": "p",
"url": "./pyodide_dev.js",
"module": "pyodide",
"evaluator": "runPython",
"pluginType": "language"
}
},
"lastExport": "2018-05-04T17:13:00.489Z"
}
%% md
# Pyodide dev notebook 🐍
An iodide notebook used for developpement that loads the locally build packages
%% plugin
{
"languageId": "py",
"displayName": "python",
"codeMirrorMode": "python",
"keybinding": "p",
"url": "./pyodide_dev.js",
"module": "pyodide",
"evaluator": "runPython",
"pluginType": "language"
}
%% js
pyodide.loadPackage('numpy')
%% code {"language":"py"}
import numpy as np
np.arange(2)
%% js
</script>
<div id='page'></div>
<script src='https://iodide.io/dist/iodide.pyodide-20180623.js'></script>
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment