New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

pythonia

Package Overview
Dependencies
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pythonia

Interop Node.js and JavaScript

Source
npmnpm
Version
0.1.0
Version published
Weekly downloads
1.5K
-29.2%
Maintainers
1
Weekly downloads
 
Created
Source

JSPyBridge

NPM version Build Status Gitpod ready-to-code

Interoperate Node.js with Python. Work in progress.

Requires Node.js 16 and Python 3.8 or newer.

Key Features

  • Async and sync function support
  • Native garbage collection
  • Callbacks
  • 🚦 Events
  • Exception handling
  • Iterator support

Limitations / WIP

  • 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.

Basic usage example

See some examples here. See documentation below and in here/.

Access JavaScript from Python

(Although the examples below use javascript import, please use JSPyBridge instead.)

pip3 install git+https://github.com/extremeheat/jspybridge.git
from JSPyBridge import require, console

chalk, fs = require("chalk"), require("fs")

console.log("Hello", chalk.red("world!"))
fs.writeFileSync("HelloWorld.txt", "hi!")

Access Python from JavaScript

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()
// The special $timeout argument tells the bridge to adjust the function call timeout
// Set Infinity for no timeout.
await root.mainloop$({ $timeout: Infinity })

Run an example

Gitpod ready-to-code

Open in Gitpod link above, and open the examples folder.

Documentation

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.

Python

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.

Usage

  • All function calls to JavaScript are thread synchronous
  • ES6 classes can be constructed without new
  • ES5 classes can be constructed with the .new psuedo method
  • Use @On decorator when binding event listeners. Use off() to disable it.
  • All callbacks run on a dedicated callback thread. DO NOT BLOCK in a callback or all other events will be blocked. Instead:
  • Use the @AsyncTask decorator when you need to spawn a new thread for an async JS task.

For more, see docs/python.md.

Basic import

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())

Event emitter

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 class

es5.js

function MyClass(num) {
    this.getNum = () => num
}
module.exports = { MyClass }

es5.py

MyEmitter = require('./es5.js')
myClass = MyClass.new(3)
print(myClass.getNum())

Iteration

items.js

module.exports = { items: [5, 6, 7, 8] }

items.py

items = require('./items.js')
for item in items:
    print(item)

Callback

callback.py

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

JavaScript

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.

  • All the Python APIs are async. You must await them all.
  • Use python.exit() or process.exit() at the end to quit the Python process.
  • This library doesn't manage the packaging.
    • Right now you need to install all the deps from pip globally, but later on we may allow loading from pip-envs.
  • When you do a normal Python function call, you can supply "positional" arguments, which must be in the correct order to what the Python function expects.
  • Some Python objects accept arbitrary keyword arguments. You can call these functions by using the special $ function syntax.
    • When you do a function call with a $ before the parenthesis, such as await some.pythonCall$(), the final argument is evaluated as a kwarg dictionary. You can supply named arguments this way.
  • Property access with a $ at the end acts as a error suppression operator.
    • Any errors will be ignored and instead undefined will be returned
  • See docs/javascript.md for more docs, and the examples for more info

Usage

Basic import

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()

FAQs

Package last updated on 12 Jul 2021

Did you know?

Socket

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.

Install

Related posts