Comments:"SheetJS Rants and Raves — Running your JS code in Python"
URL:http://blog.sheetjs.com/post/71326534924/running-your-js-code-in-python
tl;dr: PyPI package xls (repo https://github.com/SheetJS/py-xls)
I wanted to see if it was possible to run the JS XLS module from Python, partially because I needed an XLS parser in a python tool but mostly to prove to myself that it can be done.
Running small fragments of JS is easy with PyV8:
$ sudo pip install PyV8
$ python
>>> import PyV8
>>> ctx = PyV8.JSContext()
>>> ctx.enter()
>>> ctx.eval('1+1')
2
Variables are available:
>>> ctx.eval('var x = 1+1')
>>> ctx.eval('Math.pow(x,x)')
4
However, the context is bare bones. Even the mighty console is missing
>>> ctx.eval('console.log(x)')
ReferenceError: ReferenceError: console is not defined ( @ 1 : 0 ) -> console.log(x)
Fortunately, you can configure the context with your own console:
class MyConsole(PyV8.JSClass):
def log(self, *args):
print(" ".join([str(x) for x in args]))class GlobalContext(PyV8.JSClass):
console = MyConsole()ctx = PyV8.JSContext(GlobalContext())
… which works as expected:
>>> ctx.enter(); ctx.eval('console.log("foo bar baz qux")')
foo bar baz qux
For larger scripts, I found it easy to just put the entire code block within a triple-quoted string. The only caveat is that slashes have to be escaped. To automate the process, the code is separated into a python header and footer, with a Makefile to bring everything together. (note: the header ends with a triple quote and the footer starts with a triple quote)
In python code, you can access the variables in the JS scope with ctx.locals[‘…’], and numbers are readily available:
>>> ctx.eval('x = 1+1')
2
>>> ctx.locals['x']
2
however, arrays are passed as JSArray. The easiest way to capture the python values is to slice the array:
>>> ctx.eval('y = [1,2]')
<_PyV8.JSArray object at 0x106ebc5f0>
>>> ctx.locals['y']
<_PyV8.JSArray object at 0x106b82050>
>>> ctx.locals['y'][:]
[1, 2]
PyV8 automatically converts numeric and string literals from python to JS form, making it a breeze to call functions:
>>> ctx.eval('z=function(foo,bar) { return foo+bar; }')
>>> ctx.locals['z'](1,2)
3
I suspect it would be possible to build up a wrapper that emulates the entire NodeJS javascript API in PyV8, making it easy for people to bring pure-JS modules into Python. But that’s a task for another day.