python-bridge
v1.1.0
Published
Node.js to Python bridge β¨ππβ¨
Downloads
775
Readme
python-bridge
Most robust and simple Python bridge. Features, and comparisons to other Python bridges below, supports Windows.
API
View documentation with TypeScript examples.
npm install python-bridge
'use strict';
let assert = require('assert');
let pythonBridge = require('python-bridge');
let python = pythonBridge();
python.ex`import math`;
python`math.sqrt(9)`.then(x => assert.equal(x, 3));
let list = [3, 4, 2, 1];
python`sorted(${list})`.then(x => assert.deepEqual(x, list.sort()));
python.end();
var python = pythonBridge(options)
Spawns a Python interpreter, exposing a bridge to the running processing. Configurable via options
.
options.python
- Python interpreter, defaults topython
Also inherits the following from child_process.spawn([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.
var python = pythonBridge({
python: 'python3',
env: {PYTHONPATH: '/foo/bar'}
});
python`expression(args...)`
.then(...)
Evaluates Python code, returning the value back to Node.
// Interpolates arguments using JSON serialization.
python`sorted(${[6, 4, 1, 3]})`.then(x => assert.deepEqual(x, [1, 3, 4, 6]));
// Passing key-value arguments
let obj = {hello: 'world', foo: 'bar'};
python`dict(baz=123, **${obj})`.then(x => {
assert.deepEqual(x, {baz: 123, hello: 'world', foo: 'bar'});
});
python.ex`statement`
.then(...)
Execute Python statements.
let a = 123, b = 321;
python.ex`
def hello(a, b):
return a + b
`;
python`hello(${a}, ${b})`.then(x => assert.equal(x, a + b));
python.lock(...).then(...)
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.
python.lock(python => {
python.ex`hello = 123`;
return python`hello + 321'`;
}).then(x => assert.equal(x, 444));
// Recommended to define function in Python
python.ex`
def atomic():
hello = 123
return hello + 321
`;
python`atomic()`.then(x => assert.equal(x, 444));
python.stdin, python.stdout, python.stderr
Pipes going into the Python process, separate from execution & evaluation. This can be used to stream data between processes, without buffering.
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);
python.end()
Stops accepting new Python commands, and waits for queue to finish then gracefully closes the Python process.
python.disconnect()
Alias to python.end()
python.kill([signal])
Send signal to Python process, same as child_process child.kill
.
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();
});
Handling Exceptions
We can use Bluebird's promise.catch(...)
catch handler in combination with Python's typed Exceptions to make exception handling easy.
python.Exception
Catch any raised Python exception.
python.ex`
hello = 123
print(hello + world)
world = 321
`.catch(python.Exception, () => console.log('Woops! `world` was used before it was defined.'));
python.isException(name)
Catch a Python exception matching the passed name.
function pyDivide(numerator, denominator) {
return python`${numerator} / ${denominator}`
.catch(python.isException('ZeroDivisionError'), () => Promise.resolve(Infinity));
}
pyDivide(1, 0).then(x => {
assert.equal(x, Infinity);
assert.equal(1 / 0, Infinity);
});
pythonBridge.PythonException
Alias to python.Exception
, this is useful if you want to import the function to at the root of the module.
pythonBridge.isPythonException
Alias to python.isException
, this is useful if you want to import the function to at the root of the module.
Features
- 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.
Comparisons
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.
License
MIT