Skip to content

Commit d0c0b82

Browse files
authored
Merge pull request RustPython#2354 from RustPython/coolreader18/notebook-ergonomics
Add injectPython function for notebook
2 parents 4e5dc3e + c1fd81f commit d0c0b82

17 files changed

+520
-450
lines changed

wasm/.prettierrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"singleQuote": true,
3-
"tabWidth": 4
3+
"tabWidth": 4,
4+
"arrowParens": "always"
45
}

wasm/README.md

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,6 @@ To get started, install
1111
([wasm-bindgen](https://rustwasm.github.io/wasm-bindgen/whirlwind-tour/basic-usage.html)
1212
should be installed by `wasm-pack`. if not, install it yourself)
1313

14-
<!-- Using `rustup` add the compile target `wasm32-unknown-emscripten`. To do so you will need to have [rustup](https://rustup.rs/) installed.
15-
16-
```bash
17-
rustup target add wasm32-unknown-emscripten
18-
```
19-
20-
Next, install `emsdk`:
21-
22-
```bash
23-
curl https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz | tar -zxv
24-
cd emsdk-portable/
25-
./emsdk update
26-
./emsdk install sdk-incoming-64bit
27-
./emsdk activate sdk-incoming-64bit
28-
``` -->
29-
3014
## Build
3115

3216
Move into the `wasm` directory. This directory contains a library crate for

wasm/demo/webpack.config.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin');
66
const path = require('path');
77
const fs = require('fs');
88

9-
const interval = setInterval(() => console.log('keepalive'), 1000 * 60 * 5);
10-
119
module.exports = (env = {}) => {
1210
const config = {
1311
entry: './src/index.js',
@@ -52,13 +50,6 @@ module.exports = (env = {}) => {
5250
new MiniCssExtractPlugin({
5351
filename: 'styles.css'
5452
}),
55-
{
56-
apply(compiler) {
57-
compiler.hooks.done.tap('clearInterval', () => {
58-
clearInterval(interval);
59-
});
60-
}
61-
}
6253
]
6354
};
6455
if (!env.noWasmPack) {

wasm/notebook/README.md

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22

33
The RustPython Notebook is a **toy** notebook inspired by the now inactive Iodide project ([https://alpha.iodide.io/](https://alpha.iodide.io/)).
44

5-
Here is how it looks like:
5+
Here is how it looks like:
66

77
![notebook](./screenshot.png)
88

9-
You can use the notebook to experiment with using Python and Javascript in the browser together.
9+
You can use the notebook to experiment with using Python and Javascript in the browser together.
1010

1111
The main use case is for scientific communication where you can have:
12-
- text or thesis in markdown,
13-
- math with Tex,
14-
- a model or analysis written in python,
15-
- a user interface and interactive visualization with JS.
12+
13+
- text or thesis in markdown,
14+
- math with Tex,
15+
- a model or analysis written in python,
16+
- a user interface and interactive visualization with JS.
1617

1718
The Notebook loads python in your browser (so you don't have to install it) then let yous play with those languages.
1819

@@ -24,18 +25,77 @@ To read more about the reasoning behind certain features, check the blog on [htt
2425

2526
Sample notebooks are under `snippets`
2627

27-
- `snippets/python-markdown-math.txt`: python, markdown and math
28-
- `snippets/python-js.txt`, adds javascript
29-
- `snippets/python-js-css-md/` adds styling with css in separate, more organized files.
28+
- `snippets/python-markdown-math.txt`: python, markdown and math
29+
- `snippets/python-js.txt`, adds javascript
30+
- `snippets/python-js-css-md/` adds styling with css in separate, more organized files.
3031

3132
## How to use
3233

33-
- Run locally with `npm run dev`
34-
- Build with `npm run dist`
34+
- Run locally with `npm run dev`
35+
- Build with `npm run dist`
36+
37+
## JS API
38+
39+
```typescript
40+
// adds `values[name]` to the python scope under `name`
41+
function injectPython(values: { [name: string]: PythonValue });
42+
type PythonValue =
43+
// null -> None
44+
| null
45+
| undefined
46+
// n -> int(n) if Number.isInteger(n) else float(n)
47+
| number
48+
// s -> str(s)
49+
| string
50+
// typedArray -> bytes(typedArray)
51+
| Uint8Array
52+
// arr -> list(arr)
53+
| Array<PythonValue>
54+
// obj -> dict(Object.entries(obj))
55+
| { [k: string]: PythonValue }
56+
// js callback in python: positional args are passed as
57+
// arguments, kwargs is the `this` variable
58+
// f -> lambda *args, **kwargs: f.apply(kwargs, args)
59+
//
60+
// python callback in js: pass the positional args an array and
61+
// kwargs as an object
62+
// f -> (args, kwargs) => f(*args, **kwargs)
63+
| Function;
64+
65+
// appends an element to the notebook
66+
function pushNotebook(el: HTMLElement);
67+
68+
// find and displays the traceback of a python error from a callback
69+
function handlePyError(err: any);
70+
// e.g.
71+
try {
72+
pyCb([], {});
73+
} catch (err) {
74+
handlePyError(err);
75+
}
76+
```
77+
78+
`injectPython` demo:
79+
80+
```js
81+
injectPython({
82+
foo(a, b) {
83+
console.log(a);
84+
if (this.x != null) {
85+
console.log(`got kwarg! x=${this.x}`);
86+
}
87+
return (y) => y + 1 + b;
88+
},
89+
});
90+
```
91+
92+
```py
93+
adder = foo("hy from python", 3, x=[1, 2, 3])
94+
assert adder(5) == 9
95+
```
3596

3697
## Wish list / TO DO
3798

38-
- Better javascript support
39-
- Collaborative peer-to-peer editing with WebRTC. Think Google Doc or Etherpad editing but for code in the browser
40-
- `%%load` command for dynamically adding javascript libraries or css framework
41-
- Clean up and organize the code. Seriously rethink if we want to make it more than a toy.
99+
- Collaborative peer-to-peer editing with WebRTC. Think Google Doc or Etherpad editing but for code in the browser
100+
- `%%load` command for dynamically adding javascript libraries or css framework
101+
- Clean up and organize the code. Seriously rethink if we want to make it more than a toy.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* CSS */
2+
3+
@import url('https://fonts.googleapis.com/css2?family=Turret+Road:wght@700&display=swap');
4+
5+
h1 {
6+
font-family: 'Sen', 'sans-serif';
7+
}
8+
9+
h2, h3, h4, button {
10+
font-family: 'Turret Road', cursive;
11+
}
12+
13+
h1 {
14+
font-size: 1.5rem;
15+
}
16+
h2, h3, h4 {
17+
padding-top: 1.25rem;
18+
}
19+
20+
input[type='text'] {
21+
border: 1px solid black;
22+
width: 300px;
23+
height: 25px;
24+
padding: 5px;
25+
font-size: 1.2rem;
26+
font-family: 'Turret Road', cursive;
27+
}
28+
29+
hr {
30+
border: 0px;
31+
border-bottom: 1px solid black;
32+
width: 50%;
33+
margin-top: 1.5rem;
34+
margin-bottom: 1.5rem;
35+
}
36+
37+
button {
38+
background-color: black;
39+
color: white;
40+
border: 0px;
41+
font-size: 1.2rem;
42+
height: 30px;
43+
display: block;
44+
margin-top: 10px;
45+
}

wasm/notebook/snippets/python-js-css/css.txt

Lines changed: 0 additions & 42 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Javascript code goes here
2+
3+
injectPython({
4+
// injectPython functions take the positional arguments as
5+
// normal function args, and kwargs as the `this` variable
6+
add_text_input() {
7+
const input = document.createElement('input');
8+
pushNotebook(input);
9+
return () => input.value;
10+
},
11+
add_button(buttonText, cb) {
12+
const do_button = (callback) => {
13+
const btn = document.createElement('button');
14+
btn.innerHTML = buttonText;
15+
btn.addEventListener('click', () => {
16+
try {
17+
// python functions passed to js have a signature
18+
// of ([args...], {kwargs...}) => any
19+
callback([], {});
20+
} catch (err) {
21+
// puts the traceback in the error box
22+
handlePyError(err);
23+
}
24+
});
25+
pushNotebook(btn);
26+
};
27+
28+
if (cb == null) {
29+
// to allow using as a decorator
30+
return do_button;
31+
} else {
32+
do_button(cb);
33+
}
34+
},
35+
add_output() {
36+
const resultDiv = document.createElement('div');
37+
resultDiv.classList.add('result');
38+
pushNotebook(resultDiv);
39+
return (value) => {
40+
resultDiv.innerHTML = value;
41+
};
42+
},
43+
});

wasm/notebook/snippets/python-js-css/javascript.txt

Lines changed: 0 additions & 45 deletions
This file was deleted.

wasm/notebook/snippets/python-js-css/main.txt

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
# RustPython Notebook: python, javascript, css, markdown and math
44

55

6-
Python in the browser is fun. Python and Javascript working together is double the fun 😜. Markdown and math are good for communicating ideas 🤔 (especially scientific ones). Adding css to the mix, makes everything look pretty ✨.
6+
Python in the browser is fun. Python and Javascript working together is double
7+
the fun 😜. Markdown and math are good for communicating ideas 🤔 (especially
8+
scientific ones). Adding css to the mix, makes everything look pretty ✨.
79

810
---
911

@@ -22,21 +24,20 @@ In this demo example, the RustPython Notebook:
2224

2325
---
2426

25-
## Calculator
26-
### Enter your lucky number
27+
%%py
2728

28-
%%js
29+
# Python code
2930

30-
create_ui();
31+
# you have access to helpers for emitting p & h1-h6
32+
h2("Calculator")
33+
h3("Enter your lucky number")
3134

35+
inp1 = add_text_input()
36+
inp2 = add_text_input()
3237

33-
%%py
34-
# Python code
38+
@add_button("click me to add")
39+
def run_model():
40+
a, b = int(inp1()), int(inp2())
41+
set_output(f"<pre>{a} + {b} = <b>{a + b}</b></pre>")
3542

36-
def runModel():
37-
input = js_vars['data']
38-
print ("<br>")
39-
print("A little javascript birdie told the python snake your lucky number 🙊")
40-
print ("Now the everyone knows that it is: " + str(input))
41-
42-
43+
set_output = add_output()

0 commit comments

Comments
 (0)