Package: javascript
JSPyBridgeInteroperate Node.js and Python. You can run Python from Node.js,orrun Node.js from Python.Work in progress.Requires Node.js 14 and Python 3.8 or newer.Key FeaturesAbility to call async and sync functions and get object properties with a native feelBuilt-in garbage collectionBidirectional callbacks with arbitrary argumentsIteration and exception handling supportObject inspection allows you to easilyconsole.logorprint()any foreign objects(Bridge to call Python from JS) Python class extension and inheritance.See pytorch and tensorflow examples.(Bridge to call JS from Python) Native decorator-based event emitter support(Bridge to call JS from Python)First-class Jupyter Notebook/Google Colab support.See some Google Colab uses below.Basic usage exampleSee some exampleshere. Seedocumentationbelow and inhere.Access JavaScript from Pythonpip3installjavascriptfromjavascriptimportrequire,globalThischalk,fs=require("chalk"),require("fs")print("Hello",chalk.red("world!"),"it's",globalThis.Date().toLocaleString())fs.writeFileSync("HelloWorld.txt","hi!")Access Python from JavaScriptMake sure to have the dependencies installed before hand!npmipythoniaimport{python}from'pythonia'// Import tkinterconsttk=awaitpython('tkinter')// All Python API access must be prefixed with awaitconstroot=awaittk.Tk()// A function call with a $ suffix will treat the last argument as a kwarg dictconsta=awaittk.Label$(root,{text:'Hello World'})awaita.pack()awaitroot.mainloop()python.exit()// Make sure to exit Python in the end to allow node to exit. You can also use process.exit.ExamplesCheck out some cool examples below! Try them on Gitpod! Click the Open in Gitpod link above, and then open the examples folder.Bridge feature comparisonUnlike 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) bridgejavascript bridgenpm:python-bridgeGarbage 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✔✔❌Who's using itPrismarineJS/mineflayer--DocumentationFrom PythonYou can import the bridge module withfromjavascriptimportrequireThis 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 dynamicimport()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
examplerequire('chalk', '^3')to get a version greater than major version 3. Just like you would
if you were usingnpm 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 installlatestversion instead, or use the version that may already be installed globally.UsageAll function calls to JavaScript are thread synchronousES6 classes can be constructed without newES5 classes can be constructed with the .new psuedo methodUse@Ondecorator when binding event listeners. Useoff()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, seedocs/python.md.Usage👉 Click here to see some code usage examples 👈Basic importLet's say we have a file in JS like this calledtime.js...functionwhatTimeIsIt(){return(newDate()).toLocaleString()}module.exports={whatTimeIsIt}Then we can call it from Python !fromjavascriptimportrequiretime=require('./time.js')print(time.whatTimeIsIt())Event emitterYou must use the provided On, Once, decorator and off function over the normal dot methods.emitter.jsconst{EventEmitter}=require('events')classMyEmitterextendsEventEmitter{counter=0inc(){this.emit('increment',++this.counter)}}module.exports={MyEmitter}listener.pyfromjavascriptimportrequire,On,offMyEmitter=require('./emitter.js')# New class instancemyEmitter=MyEmitter()# Decorator usage@On(myEmitter,'increment')defhandleIncrement(this,counter):print("Incremented",counter)# Stop listening. `this` is the this variable in JS.off(myEmitter,'increment',handleIncrement)# Trigger the event handlermyEmitter.inc()ES5 classes5.jsfunctionMyClass(num){this.getNum=()=>num}module.exports={MyClass}es5.pyMyEmitter=require('./es5.js')myClass=MyClass.new(3)print(myClass.getNum())Iterationitems.jsmodule.exports={items:[5,6,7,8]}items.pyitems=require('./items.js')foriteminitems:print(item)Callbackcallback.jsexportfunctionmethod(cb,salt){cb(42+salt)}callback.pymethod=require('./callback').method# Example with a lambda, but you can also pass a function refmethod(lambdav:print(v),2)# Prints 44From JavaScriptAll the Python APIs are async. You must await them all.Usepython.exit()orprocess.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 asawait 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 returnedSeedocs/javascript.mdfor more docs, and the examples for more infoUsage👉 Click here to see some code usage examples 👈Basic importLet's say we have a file in Python like this calledtime.py...importdatetimedefwhat_time_is_it():returnstr(datetime.datetime.now())Then we can call it from JavaScript !import{python}from'pythonia'consttime=awaitpython('./time.py')console.log("It's",awaittime.what_time_is_it())python.exit()IteratingWhen iterating a Python object, youmustuse afor awaitloop instead of a normalfor-ofloop.iter.pyimportosdefget_files():forfinos.listdir():yieldfiter.jsconstiter=awaitpython('./iter.py')constfiles=awaititer.get_files()forawait(constfileoffiles){console.log(file)}DetailsWhen doing a function call, any returned foreign objects will be sent to you as a reference. For example, if you're in JavaScript and do a function call to Python that returns an array, you won't get a JS array back, but you will get a reference to the Python array. You can still access the array normally with the [] notation, as long as you use await.This behavior makes it very fast to pass objects directly between same-language functions, avoiding costly cross-language data transfers.However, this does not apply with callbacks or non-native function input parameters. The bridge will try to serialize what it can, and will give you a foreign reference if it's unable to serialize something. So if you pass a JS object, you'll get a Python dict, but if the dict contains something like a class, you'll get a reference in its place.If you would like the bridge to turn a foreign reference to something native, you can use.valueOf()to transfer an object via JSON serialization, or.blobValueOf()to write an object into the communication pipe directly..valueOf()can be used on any JSON-serializable object, but may be very slow for big data..blobValueOf()can be used on any pipe-writeable object implementing thelengthproperty (e.g.Buffer). It can be massively faster by circumventing the JSON+UTF8 encode/decode layer, which is inept for large byte arrays.You can use custom Node.js/Python binary paths by setting theNODE_BINorPYTHON_BINenviornment variables before importing the library. Otherwise, thenodeandpython3orpythonbinaries will be called relative to your PATH enviornment variable.The inter-process communication can be inspected by setting theDEBUGenv var tojspybridge.LimitationsTheffidkeyword is reserved. You cannot use it in variable names, object keys or values as this is used to internlly track objects.On the bridge to call JavaScript from Python, due to the limiatations of Python and cross-platform IPC, we currently communicate over standard error which means that specific output in JS standard error can interfere with the bridge (as of this writing, the prefices{"r"andblob!are reserved). A similar issue exists on Windows with Python. You are however very unlikely to have issues with this.Function calls will timeout after 100000 ms and throw aBridgeExceptionerror. That default value can be overridden by defining the new value ofREQ_TIMEOUTin an environment variable.