Skip to content

Instantly share code, notes, and snippets.

@wiwichips
Created August 10, 2023 21:21
Show Gist options
  • Save wiwichips/acbb1b28f658a1c757e251c6271fe047 to your computer and use it in GitHub Desktop.
Save wiwichips/acbb1b28f658a1c757e251c6271fe047 to your computer and use it in GitHub Desktop.
How to Call JavaScript Functions in Python

Using PythonMonkey to Execute JavaScript Code in Python

What is PythonMonkey?

PythonMonkey (GitHub Link) is a Python library designed to integrate the Python and JavaScript ecosystems. It uses Mozilla’s SpiderMonkey engine to execute JavaScript and WebAssembly within Python. This approach allows Python developers to interact with JavaScript libraries, modules, and WebAssembly more efficiently.

Read the Release article: Run JavaScript/WASM in Python: high-level SpiderMonkey bindings to Python with PythonMonkey

Here's a quick demo that uses JavaScript's console.log function for printing to the console from Python.

import pythonmonkey
pythonmonkey.eval("console.log('Hello from JavaScript!');")

Installing PythonMonkey

PythonMonkey requires npm to be installed as well as pip, then install with:

pip install pythonmonkey

Calling a JavaScript Function from Python

In this example, we'll define a function in a JavaScript file that will add two numbers and then we'll import this JavaScript file into Python and execute it!

// lib.js
function add(a, b) {
    return a + b;
}

exports.add = add;
# main.py
import pythonmonkey
js_lib = pythonmonkey.require('./lib')

print( js_lib.add(1,2) ) # 3.0
print( js_lib.add(99,1) ) # 100.0

Evaluating JavaScript Code Directly in Python

This example shows a JavaScript object getting defined in JavaScript using pythonmonkey.eval. This function evaluated JavaScript code in a JavaScript context and returns it to Python if its a statement.

# This example demonstrates passing an object defined in JavaScript to Python
# and using pythonmonkey.eval to evaluate JavaScript code.

import pythonmonkey as pm

js_obj = pm.eval("({ a: 1, b: () => 2 })")

print(js_obj) # {'a': 1.0, 'b': <built-in method JSFunctionCallable of tuple object at 0x7b5d50926f80>}
print(js_obj.a) # 1.0
print(js_obj['a']) # 1.0
print(js_obj.b()) # 2.0

Using JavaScript Promises and the JS Event Loop in Python

We'll use Python's asyncio to provide an event loop for JavaScript to execute Promises. This allows Python developers to use a familiar library for executing asynchronous JavaScript code!

# This example will demonstrate using asyncio to await a promise which resolves
# after 2 seconds.

import pythonmonkey
import asyncio

async def someAsyncStuff():
    two_sec_promise = pythonmonkey.eval("""
        new Promise((resolve, reject) => {
            setTimeout(resolve, 2000);
        });
    """)

    print("before calling the 2 second timer")
    await two_sec_promise
    print("two seconds have now passed")

asyncio.run(someAsyncStuff())

Using WebAssembly in Python

Using PythonMonkey, we can execute WebAssembly code in Python and call functions from WebAssembly exports directly from Python. Refer to the following example as reference:

# Executing WebAssembly from Python
# 
# PythonMonkey uses SpiderMonkey, which is a JavaScript and WebAssembly engine.
# Using the JavaScript WebAssembly API and calling it from Python using PythonMonkey,
# we can execute WebAssembly easily in Python.
#
# Refer to the MDN documentation for more information on WebAssembly.instantiate
# https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/instantiate

import pythonmonkey
import asyncio
WebAssembly = pythonmonkey.WebAssembly

# This is some WebAssembly code which calculates the factorial in a function called
# fac. Typically you would read a file in python and not hard code the bytes into
# the file like this.
wasm_bytes = bytearray(b'\x00asm\x01\x00\x00\x00\x01\x06\x01`\x01|\x01|\x03\x02\x01\x00\x07\x07\x01\x03fac\x00\x00\n.\x01,\x00 \x00D\x00\x00\x00\x00\x00\x00\xf0?c\x04|D\x00\x00\x00\x00\x00\x00\xf0?\x05 \x00 \x00D\x00\x00\x00\x00\x00\x00\xf0?\xa1\x10\x00\xa2\x0b\x0b\x00\x12\x04name\x01\x06\x01\x00\x03fac\x02\x03\x01\x00\x00')

async def getWasmExports(wasm_bytes):
    # WebAssembly.instantiate returns a promise which we'll await in python
    return (await WebAssembly.instantiate(wasm_bytes, {})).instance.exports

my_wasm_module = asyncio.run(getWasmExports(wasm_bytes))

# execute WebAssembly code in Python!
print(my_wasm_module.fac(4)) # this outputs "24.0" since factorial(4) == 24
print(my_wasm_module.fac(5)) # this outputs "120.0"
print(my_wasm_module.fac(6)) # this outputs "720.0"

Check out the following articles for more WebAssembly in Python using PythonMonkey examples:

Conclusion

PythonMonkey is a groundbreaking library that bridges the Python and JavaScript ecosystems, allowing seamless execution of JavaScript and WebAssembly within Python. Leveraging Mozilla’s SpiderMonkey engine, PythonMonkey ensures high performance, making it possible to harness JavaScript libraries, modules, and even WebAssembly in Python without a significant performance hit. Whether you're importing npm packages, executing WebAssembly, or running JavaScript functions directly in Python, PythonMonkey delivers a smooth experience.

If you like PythonMonkey, consider giving it a star on Github! https://github.com/Distributive-Network/PythonMonkey

Enjoy using PythonMonkey to execute JavaScript code or WASM in Python!

Video version of this article: https://youtu.be/UmlSryXsh30

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment