Node v10 or above is required
Tested with python 3.6 - 3.8.
BEFORE NPM INSTALL OR YARN INSTALL
- Make sure
python
in your systemPATH
is the correct one:python --version
. You may use a virtualenv to do this. - Install gyp-next:
git clone https://github.com/nodejs/gyp-next
;cd gyp-next
;python setup.py install
yarn add @fridgerator/pynode
ornpm install @fridgerator/pynode
- If your default
python
is version 2.7, then you may have to yarn install using additional env variables:PY_INCLUDE=$(python3.6 build_include.py) PY_LIBS=$(python3.6 build_ldflags.py) yarn
In a python file example.py
:
def add(a, b):
return a + b
in node:
const pynode = require('@fridgerator/pynode')
// Workaround for linking issue in linux:
// https://bugs.python.org/issue4434
// if you get: `undefined symbol: PyExc_ValueError` or `undefined symbol: PyExc_SystemError`
pynode.dlOpen('libpython3.6m.so') // your libpython shared library
// optionally pass a path to use as Python module search path
pynode.startInterpreter()
// add current path as Python module search path, so it finds our test.py
pynode.appendSysPath('./')
// open the python file (module)
pynode.openFile('example')
// call the python function and get a return value
pynode.call('add', 1, 2, (err, result) => {
if (err) return console.log('error : ', err)
result === 3 // true
})
The Full Object API allows full interaction between JavaScript objects within Python code, and Python objects within JavaScript code. In Python, this works very transparently without needing to know if a value is a Python or JS object. In JavaScript, the interface is (currently) a bit more primitive, but supports getting object members and calling objects using .get()
and .call()
.
Primitives are generally converted between JS and Python values, so passing strings, numbers, lists, and dicts generally works as expected. The wrappers described above only kick in for non-primitives.
In a python file test_files/objects.py
:
def does_things_with_js(jsobject):
'''This function demonstrates that we can treat passed-in
JavaScript objects (almost) as if they were native Python objects.
'''
# We can print out JS objects; toString is called automatically
# (it is mapped to Python's __str__):
print("does_things_with_js:", jsobject)
# We can call any function on the JS object. Objects passed
# in are converted to JS if they are primitives, or wrapped in the
# Full Object API if not, so JavaScript can also call back into Python:
result = jsobject.arbitraryFunction("nostrongtypinghere")
# The result from JavaScript is also converted back into Python:
print(result)
# The result will be converted back into JavaScript by PyNode:
return result
class PythonClass(object):
'''A simple class to demonstrate creating and calling an instance from JavaScript.'''
def __init__(self, a, b):
'''Constructor. a and b are converted to Python types by PyNode'''
self.a = a
self.b = b
def collate(self, callback):
'''A function that is given a callback to call into. This can be a JavaScript function'''
print('PythonClass.collate()')
print('callback:', callback)
# Arguments are converted into JavaScript objects, and the return value
# from the callback is converted back into Python objects.
return callback(self.a, self.b, "hello", 4)
def __repr__(self):
return 'PythonClass(a=%r, b=%r)' % (self.a, self.b)
In Node:
const pynode = require('@fridgerator/pynode')
pynode.startInterpreter();
pynode.appendSysPath('./test_files/');
/* Modules are imported as JS objects, implementing the Full Object API */
const objectsmodule = pynode.import('objects'); /* test module in test_files */
const python_builtins = pynode.import('builtins'); /* access to python builtins such as str, all, etc, if you want them. */
/* Define an example JavaScript class: */
class JSClass {
constructor(item) {
this.item = item
}
arbitraryFunction(value) {
console.log("arbitraryFunction called; item = " + this.item);
console.log(" value = " + value);
return value + 12;
}
toString() {
return "JSClass{item=" + this.item + "}"
}
}
/* Create an instance of JSClass which Python will have access to: */
jsclassinstance = new JSClass(['some', 'data']);
console.log(jsclassinstance);
/* Get the 'does_things_with_js' Python function from the objects module,
and call it, passing in the JSClass object: */
result = objectsmodule.get('does_things_with_js').call(jsclassinstance);
/* Python objects are automatically converted back to JS, or returned as
PyNodeWrappedPythonObject instances (see below( if they are
non-primitive types:
*/
console.log(result);
/* Create a Python class. In Python this is done by calling the class
object with constructor arguments (ie, don't use `new`): */
somedict = {'some': 'dict'};
pyclassinstance = objectsmodule.get('PythonClass').call(somedict, '2');
/* The resulting object is returned as a PyNodeWrappedPythonObject instance,
which implements the Full Object API: */
console.log(pyclassinstance);
/* The PyNodeWrappedPythonObject instance can have members retrieved from it
and functions called. Below is the equivalent of (in Python)
`getattr(pyclassinstance, 'collate').__call__(<some javascript function>)`
or put simply:
`pyclassinstance.collate(<some javascript function>)`.
The JavaScript function is also converted to a callable Python object */
*/
result = pyclassinstance.get('collate').call(function(a, b, c, d) {
console.log(arguments);
return c * d; /* JavaScript return values are converted back to Python types by PyNode */
});
console.log(result);