Node.js to Python bridge ✨🐍🚀✨
npm install @fisch0920/python-bridgeMost robust and simple Python bridge. Features, and comparisons to other Python bridges below, supports Windows.
View documentation with TypeScript examples.
```
npm install python-bridge
`javascript
'use strict';
let assert = require('assert');
let pythonBridge = require('python-bridge');
let python = pythonBridge();
python.eximport math;math.sqrt(9)
python.then(x => assert.equal(x, 3));
let list = [3, 4, 2, 1];
pythonsorted(${list}).then(x => assert.deepEqual(x, list.sort()));
python.end();
`
Spawns a Python interpreter, exposing a bridge to the running processing. Configurable via options.
* options.python - Python interpreter, defaults to python
Also inherits the following from [child_process.spawn([options])](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options).
* options.cwd - String Current working directory of the child processoptions.env
* - Object Environment key-value pairsoptions.stdio
* - Array Child's stdio configuration. Defaults to ['pipe', process.stdout, process.stderr]options.uid
* - Number Sets the user identity of the process.options.gid
* - Number Sets the group identity of the process.
`javascript`
var python = pythonBridge({
python: 'python3',
env: {PYTHONPATH: '/foo/bar'}
});
expression(args...) .then(...)Evaluates Python code, returning the value back to Node.
`javascriptsorted(${[6, 4, 1, 3]})
// Interpolates arguments using JSON serialization.
python.then(x => assert.deepEqual(x, [1, 3, 4, 6]));
// Passing key-value arguments
let obj = {hello: 'world', foo: 'bar'};
pythondict(baz=123, **${obj}).then(x => {`
assert.deepEqual(x, {baz: 123, hello: 'world', foo: 'bar'});
});
statement .then(...)Execute Python statements.
`javascript
let a = 123, b = 321;
python.ex
def hello(a, b):
return a + b;hello(${a}, ${b})
python.then(x => assert.equal(x, a + b));`
Locks access to the Python interpreter so code can be executed atomically. If possible, it's recommend to define a function in Python to handle atomicity.
`javascripthello = 123
python.lock(python => {
python.ex;hello + 321'
return python;
}).then(x => assert.equal(x, 444));
// Recommended to define function in Python
python.ex
def atomic():
hello = 123
return hello + 321;atomic()
python.then(x => assert.equal(x, 444));`
Pipes going into the Python process, separate from execution & evaluation. This can be used to stream data between processes, without buffering.
`javascript
let Promise = require('bluebird');
let fs = Promise.promisifyAll(require('fs'));
let fileWriter = fs.createWriteStream('output.txt');
python.stdout.pipe(fileWriter);
// listen on Python process's stdout
python.ex
import sys
for line in sys.stdin:
sys.stdout.write(line)
sys.stdout.flush().then(function () {
fileWriter.end();
fs.readFileAsync('output.txt', {encoding: 'utf8'}).then(x => assert.equal(x, 'hello\nworld\n'));
});
// write to Python process's stdin
python.stdin.write('hello\n');
setTimeout(() => {
python.stdin.write('world\n');
python.stdin.end();
}, 10);
`
Stops accepting new Python commands, and waits for queue to finish then gracefully closes the Python process.
_Alias to python.end()_
Send signal to Python process, same as child_process child.kill.
`javascript
let Promise = require('bluebird');
python.ex
from time import sleep
sleep(9000).timeout(100).then(x => {`
assert.ok(false);
}).catch(Promise.TimeoutError, (exit_code) => {
console.error('Python process taking too long, restarted.');
python.kill('SIGKILL');
python = pythonBridge();
});
We can use Bluebird's promise.catch(...) catch handler in combination with Python's typed Exceptions to make exception handling easy.
Catch any raised Python exception.
`javascript
python.ex
hello = 123
print(hello + world)
world = 321.catch(python.Exception, () => console.log('Woops! world was used before it was defined.'));`
Catch a Python exception matching the passed name.
`javascript${numerator} / ${denominator}
function pyDivide(numerator, denominator) {
return python`
.catch(python.isException('ZeroDivisionError'), () => Promise.resolve(Infinity));
}
pyDivide(1, 0).then(x => {
assert.equal(x, Infinity);
assert.equal(1 / 0, Infinity);
});
_Alias to python.Exception, this is useful if you want to import the function to at the root of the module._
_Alias to python.isException, this is useful if you want to import the function to at the root of the module._
----
* Does not affect Python's stdin, stdout, or stderr pipes.
* Exception stack traces forwarded to Node for easy debugging.
* Python 2 & 3 support, end-to-end tested.
* Windows support, end-to-end tested.
* Command queueing, with promises.
* Long running Python sessions.
* ES6 template tags for easy interpolation & multiline code.
After evaluating of the existing landscape of Python bridges, the following issues are why python-bridge was built.
* python-shell — No promises for queued requests; broken evaluation parser; conflates evaluation and stdout; complex configuration.
* python — Broken evaluation parsing; no exception handling; conflates evaluation, stdout, and stderr.
* node-python — Complects execution protocol with incomplete Python embedded DSL.
* python-runner — No long running sessions; child_process.spawn` wrapper with unintuitive API; no serialization.
* python.js — Embeds specific version of CPython; requires compiler and CPython dev packages; incomplete Python embedded DSL.
* cpython — Complects execution protocol with incomplete Python embedded DSL.
* eval.py — Can only evaluate single line expressions.
* py.js — For setting up virtualenvs only.
MIT