Skip to content

Commit

Permalink
docs
Browse files Browse the repository at this point in the history
  • Loading branch information
DerThorsten committed Mar 27, 2024
1 parent 560be08 commit 4d92080
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 13 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,43 @@ pyjs is modern [pybind11](https://github.com/pybind/pybind11) + emscripten [Emb
Python <-> JavaScript foreign function interface (FFI) for wasm/emscripten compiled Python.

The API is loosly based on the FFI of [pyodide](https://pyodide.org/en/stable/usage/type-conversions.html).


## Quickstart

Access Javascript from Python:

```python
import pyjs

# hello world
pyjs.js.console.log("Hello, World!")

# create a JavaScript function to add two numbers
js_function = pyjs.js.Function("a", "b", """
console.log("Adding", a, "and", b)
return a + b
""")

# call the function
result = js_function(1, 2)
```

Access Python from Javascript:

```JavaScript
// hello world
pyjs.eval("print('Hello, World!')")

// eval a python expression and get the result
const py_list = pyjs.eval("[i for i in range(10)]")

/// access
console.log(py_list.get(0)) // same as py_list[0] on the python side
```

## Full Documentation
See the [documentation](https://emscripten-forge.github.io/pyjs/) for a full documentation.

## Try it out
To try it out, you can use the [playground](https://emscripten-forge.github.io/pyjs/lite/).
5 changes: 5 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,8 @@ const py_list = pyjs.eval("[i for i in range(10)]")
/// access
console.log(py_list.get(0)) // same as py_list[0] on the python side
```

## Try it out

To try it out, you can use [jupyterlite](../lite),
the [JavaScript REPL](../try-pyjs-from-javaScript) or the [Python REPL](../try-pyjs-from-python).
2 changes: 0 additions & 2 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

## Prerequisites:

https://repo.mamba.pm/emscripten-forge.
Before we start, lets introduce a few concepts and tools that are used in the pyjs workflow.
### Conda-forge Emscripten-Forge

Expand All @@ -12,7 +11,6 @@ Before we start, lets introduce a few concepts and tools that are used in the py
https://github.com/emscripten-forge/empack is a tool to "pack" conda environments into a set of files that can be consumed by pyjs.



## Installation Steps

So we assume there is a directory called `/path/to/deploy` where we will
Expand Down
1 change: 1 addition & 0 deletions docs/try_from_js.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@


# Try pyjs from JavaScript
For a full fledged example have a look at the [jupyterlite-deployment](../lite).

<iframe
src="../lite/repl/index.html?kernel=xjavascript&theme=JLDracula&code=importScripts(`../../../../xeus/bin/pyjs_runtime_browser.js`);%0A
Expand Down
1 change: 1 addition & 0 deletions docs/try_from_py.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Try pyjs from Python
For a full fledged example have a look at the [jupyterlite-deployment](../lite).
<iframe
src="../lite/repl/index.html?kernel=xpython&theme=JLDracula&code=import pyjs"
width="100%"
Expand Down
128 changes: 124 additions & 4 deletions examples/js_api_tour.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
// %% [markdown]
// Welcome to the tour of the PYJS JavaScript API. This notebook demonstrates how to use the PYJS JavaScript API to run Python code in the browser.
// # pyjs JavaScript API Tour
// Welcome to the tour of the pyjs JavaScript API. This notebook demonstrates how to use the PYJS JavaScript API to run Python code in the browser.

// %% [code]
// %% [markdown]
// # Loading the pyjs module

// %% [code]
// load the pyjs runtime by importing the pyjs_runtime_browser.js file
// the url differs depending on the deployment
importScripts("../../../../xeus/bin/pyjs_runtime_browser.js");

// the locateFile function is used to locate the wasm file
// which sits next to the pyjs_runtime_browser.js file
// in thism deployment
let locateFile = function(filename){
if(filename.endsWith('pyjs_runtime_browser.wasm')){
return `../../../../xeus/bin/pyjs_runtime_browser.wasm`;
}
};

// the createModule function in from the pyjs runtime
// is used to create the pyjs module
let pyjs = await createModule({locateFile:locateFile});

// load the python packages (includung the python standard library)
// from the empack environment
packages_json_url = "../../../../xeus/kernels/xpython/empack_env_meta.json"
package_tarballs_root_url = "../../../../xeus/kernel_packages/"
await pyjs.bootstrap_from_empack_packed_environment(
Expand All @@ -19,12 +33,118 @@ await pyjs.bootstrap_from_empack_packed_environment(
);


// %% [code]
// %% [markdown]
// # Evaluating Python expressions:
// From now on, you can use the pyjs module to run python code.
// Here we use "eval" to evaluate a python expression

// %% [code]
pyjs.eval("print('hello world')");

// %% [markdown]
// # Executing Python code
// Here we execute a python code block using "exec" function

// %% [code]
pyjs.exec(`
import numpy
print(numpy.random.rand(3))
`);
`);

// %% [markdown]
// # Executing Python code and returning the last expression
// Here we execute a python code block using "exec" function and return the last expression.


// %% [code]
let rand_arr = pyjs.exec_eval(`
import numpy
numpy.random.rand(2,4,3)
`);
rand_arr.delete()

// %% [markdown]

// # Using the pyobject class
// When a python object is returned, it is wrapped in a pyobject class.
// This class provides methods to interact with the python object.
// Any created instance of the pyobject class needs to be deleted using the "delete" method.

// %% [code]
// create a numpy array with [0,1,2,3] as value
let arr = pyjs.exec_eval(`
import numpy
numpy.arange(0,4)
`);

// get the shape
let arr_shape = arr.shape

// get the square function
const square_func = pyjs.eval('numpy.square')

// any function call / __call__ like operator on the python side
// is called via "py_call"
const res = sin_func.py_call(arr)

// print the result
console.log(res)

// delete all the created pyobjects
res.delete()
square_func.delete()
arr_shape.delete()
arr.delete()

// %% [markdown]
// # Type Conversion
// pyjs provides methods to convert between JavaScript and Python types.
// ## Explicit conversion

// %% [code]
// python list to javascript array
const py_list = pyjs.eval("[1,2,3]")
const js_arr = pyjs.to_js(py_list)
py_list.delete()
console.log(js_arr)

// python dict to js map
const py_dict = pyjs.eval("dict(a=1, b='fobar')")
const js_map = pyjs.to_js(py_dict)
py_dict.delete()

// values
console.log(Array.from(js_map.keys()))
// keys
console.log(Array.from(js_map.values()))

// %% [markdown]
// ## Implicit conversion
// Fundamental types are automatically converted between Python and JavaScript.
// This includes numbers, strings, booleans and null.

// %% [code]
// sum is a plain javascript number
const sum = pyjs.eval("sum([i for i in range(0,101)])")
sum

// is_true is a plain javascript boolean
const is_true = pyjs.eval("sum([i for i in range(0,101)]) == 5050")
is_true

// none will be undefined
let none = pyjs.eval('None')
console.log(none)

// %% [markdown]
// # Asynchronous execution
// The pyjs module provides a way to run python code asynchronously using the "exec_async" function.

// %% [code]
const py_code = `
import asyncio
await asyncio.sleep(2)
sum([i for i in range(100)])
`
result = await pyjs.async_exec_eval(py_code)
console.log(result); // 4950
90 changes: 83 additions & 7 deletions examples/py_api_tour.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# %% [markdown]
# A tour of the Python API
# ========================
# # A tour of the Python API


# %% [code]
Expand All @@ -9,8 +8,8 @@


# %% [markdown]
# Accessing the the JavaScript global object
# ------------------------------------------
# # Accessing the the JavaScript global object
#
# The global object in javascript is accessible via `pyjs.js`.
# Since this example runs **not** in the main thread, but only
# in a worker thread, we can not acces the window object, but
Expand All @@ -26,8 +25,7 @@


# %% [markdown]
# Create JavaScript functions on the fly
# --------------------------------------
# # Create JavaScript functions on the fly
#
# To create a javascript fuction like the following
#
Expand All @@ -46,4 +44,82 @@
# %% [code]
# call the function
result = js_function(1, 2)
result
result

# %% [markdown]
# # Type conversion
#
# Pyjs allows to convert between python and javascript types.
#
# ## Explicit conversion

# %% [code]
# convert a python list to a javascript array
js_list = pyjs.js.eval("[1,2,3]")
# pylist is a vanilla python list
py_list = pyjs.to_py(js_list)
py_list

# %% [code]
# convert a nested javascript object to a python dict
js_nested_object = pyjs.js.Function("return{ foo:42,bar:[1,{a:1,b:2}]};")()
py_dict = pyjs.to_py(js_nested_object)
py_dict

# ### Custom converters
#
# Pyjs allows to register custom converters for specific javascript classes.

# %% [code]
# Define JavaScript Rectangle class
# and create an instance of it
rectangle = pyjs.js.Function("""
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
return new Rectangle(10,20)
""")()

# A Python Rectangle class
class Rectangle(object):
def __init__(self, height, width):
self.height = height
self.width = width

# the custom converter
def rectangle_converter(js_val, depth, cache, converter_options):
return Rectangle(js_val.height, js_val.width)

# Register the custom converter
pyjs.register_converter("Rectangle", rectangle_converter)

# Convert the JavaScript Rectangle to a Python Rectangle
r = pyjs.to_py(rectangle)
assert isinstance(r, Rectangle)
assert r.height == 10
assert r.width == 20
# %%

# %% [markdown]
# ## Implicit conversion
# ## Implicit conversion
# Fundamental types are automatically converted between Javascript and Python.
# This includes numbers, strings, booleans and undefined and null.

# %% [code]
# this will convert the javascript string to a python string
origin = pyjs.js.location.origin
assert isinstance(origin, str)

# or results from a javascript function
js_function = pyjs.js.Function("a", "b", "return a + b")
result = js_function("hello", "world")
assert isinstance(js_function("hello", "world"), str)
assert isinstance(js_function(1, 2), int)
assert isinstance(js_function(1.5, 2.0), float)
assert isinstance(js_function(1.5, 2.5), int) # (!)

# %%

0 comments on commit 4d92080

Please sign in to comment.