
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
Interoperate Node.js and Python. Work in progress.
Requires Node.js 16 and Python 3.8 or newer.
console.log or print() any foreign objectsSee some examples here. See documentation below and in here.
pip3 install javascript
from javascript import require, globalThis
chalk, fs = require("chalk"), require("fs")
print("Hello", chalk.red("world!"), "it's", globalThis.Date().toLocaleString())
fs.writeFileSync("HelloWorld.txt", "hi!")
Make sure to have the dependencies installed before hand!
npm i pythonia
import { python } from 'pythonia'
// Import tkinter
const tk = await python('tkinter')
// All Python API access must be prefixed with await
const root = await tk.Tk()
// A function call with a $ suffix will treat the last argument as a kwarg dict
const a = await tk.Label$(root, { text: 'Hello World' })
await a.pack()
await root.mainloop()
python.exit() // Make sure to exit Python in the end to allow node to exit. You can also use process.exit.
Check out some cool examples below! Try them on Gitpod! Click the Open in Gitpod link above, and then open the examples folder.
Unlike other bridges, you may notice you're not just writing Python code in JavaScript, or vice-versa. You can operate on objects on the other side of the bridge as if the objects existed on your side. This is achieved through real interop support: you can call callbacks, and do loss-less function calls with any arguments you like (with the exception of floating points percision of course).
| python(ia) bridge | javascript bridge | npm:python-bridge | |
|---|---|---|---|
| Garbage collection | ✔ | ✔ | ❌ |
| Class extension support | ✔ | Not built-in (rare use case), can be manually done with custom proxy | ❌ |
| Passthrough stdin | ❌ (Standard input is not piped to bridge processes. Instead, listen to standard input then expose an API on the other side of the bridge recieve the data.) | ❌ | ✔ |
| Passthrough stdout, stderr | ✔ | ✔ | ✔ |
| Long-running sync calls | ✔ | ✔ | ✔ |
| Long-running async calls | ❌ (need to manually create new thread) | ✔ (AsyncTask) | ❌ (need to manually create new thread) |
| Callbacks | ✔ | ✔ | ❌ |
| Call classes | ✔ | ✔ | |
| Iterators | ✔ | ✔ | ❌ |
| Inline eval | ✔ | ❌ | |
| Dependency Management | ❌ | ✔ | ❌ |
| Local File Imports | ✔ | ✔ | ❌ |
| Error Management | ✔ | ✔ | ✔ |
| Object inspection | ✔ | ✔ | ❌ |
This bridge works through standard input/output pipes, there are no native modules and the communication can happen through anywhere--either pipes or network sockets.
You can import and call any JS or Python class you want, with few exceptions.
How it works. For every property access, there is a communication protocol where one side may access properties on the other, and also complete function calls. Non-primitive values are sent as foreign object reference IDs (FFID). These FFIDs exist in a map on both sides of the bridge, and map numeric IDs with a object reference.
On the opposite side to the one which holds a reference, this FFID is assigned to a Proxy object.
In JS, a ES6 proxy is used, and in Python, the proxy is a normal class with custom __getattr__
and other magic methods. Each proxy property access is mirrored on the other side of the bridge.
Proxy objects on both sides of the bridge are GC tracked. In JavaScript, all python Proxy objects
are registered to a FinalizationRegistry. In Python, __del__ is used to track the Proxy object's
destruction. When the proxy object is destoryed on one side of the bridge, its refrence is removed
from the other side of the bridge. This means you don't have to deal with memory management.
You can import the bridge module with
from javascript import require
This will import the require function which you can use just like in Node.js. This is a slightly
modified require function which does dependency management for you. The first paramater is the name
or location of the file to import. Internally, this calls the ES6 dynamic import() function. Which
supports both CommonJS and ES6 modules.
If you are passing a module name (does not start with / or include a .) such as 'chalk', it will search for the dependency in the internal node_module folder and if not found, install it automatically. This install will only happen once, it won't impact startup afterwards.
The second paramater to the built-in require function is the version of the package you want, for
example require('chalk', '^3') to get a version greater than major version 3. Just like you would
if you were using npm install. It's reccomended to only use the major version as the name and version
will be internally treated as a unique package, for example 'chalk--^3'. If you leave this empty,
we will install latest version instead, or use the version that may already be installed globally.
@On decorator when binding event listeners. Use off() to disable it.For more, see docs/python.md.
Let's say we have a file in JS like this called time.js ...
function whatTimeIsIt() {
return (new Date()).toLocaleString()
}
module.exports = { whatTimeIsIt }
Then we can call it from Python !
from javascript import require
time = require('./time.js')
print(time.whatTimeIsIt())
You must use the provided On, Once, decorator and off function over the normal dot methods.
emitter.js
const { EventEmitter } = require('events')
class MyEmitter extends EventEmitter {
counter = 0
inc() {
this.emit('increment', ++this.counter)
}
}
module.exports = { MyEmitter }
listener.py
from javascript import require, On, off
MyEmitter = require('./emitter.js')
# New class instance
myEmitter = MyEmitter()
# Decorator usage
@On(myEmitter, 'increment')
def handleIncrement(this, counter):
print("Incremented", counter)
# Stop listening. `this` is the this variable in JS.
off(myEmitter, 'increment', handleIncrement)
# Trigger the event handler
myEmitter.inc()
es5.js
function MyClass(num) {
this.getNum = () => num
}
module.exports = { MyClass }
es5.py
MyEmitter = require('./es5.js')
myClass = MyClass.new(3)
print(myClass.getNum())
items.js
module.exports = { items: [5, 6, 7, 8] }
items.py
items = require('./items.js')
for item in items:
print(item)
callback.js
export function method(cb, salt) {
cb(42 + salt)
}
callback.py
method = require('./callback').method
# Example with a lambda, but you can also pass a function ref
method(lambda v: print(v), 2) # Prints 44
The magic behind this is the usage of Proxy chains which permits call stack build up, until a .then call for property access or a function call is done. Afterwards, the callstack is sent and executed in Python.
python.exit() or process.exit() at the end to quit the Python process.$ function syntax.
$ before the parenthesis, such as await some.pythonCall$(),
the final argument is evaluated as a kwarg dictionary. You can supply named arguments this way.Let's say we have a file in Python like this called time.py ...
import datetime
def what_time_is_it():
return str(datetime.datetime.now())
Then we can call it from JavaScript !
import { python } from 'pythonia'
const time = await python('./time.py')
console.log("It's", await time.what_time_is_it())
python.exit()
for await loop instead of a normal for-of loop.iter.py
import os
def get_files():
for f in os.listdir():
yield f
iter.js
const iter = await python('./iter.py')
const files = await iter.get_files()
for await (const file of files) {
console.log(file)
}
FAQs
Bridge to call and interop Python APIs from Node.js
The npm package pythonia receives a total of 1,377 weekly downloads. As such, pythonia popularity was classified as popular.
We found that pythonia demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.