I am posting this piece of code as it might be of interest to other people following the same route. I cannot evolve it into a contribution, because there is too much complication in getting arrays working.
I used javascript Proxy to route requests to Python objects.
`"""
Very Limited integration of data with QuickJS.
Proxy data from Python into Javascript rather than converting to
JSON and back. This means that unused data is not transformed.
This uses the Javascript Proxy Handler Get/Set to access data in
python objects/dicts.
There is minimal support for lists. Javascript does not have a proxy
for Arrays, and creating a new Array type in Javascript is beyond the
needs of my project.
"""
from quickjs import Context, Object as QuickJSObject
import json
from pprint import pp
from uuid import UUID
from datetime import date, datetime
class JS:
interp = None
# Store global variables here. Reference from javascript by path
_globals = None
# Used for generating unique ids in Context namespace
_incr = 0
# I cache the values passed from python to js. Otherwise, we create new representation
# objects each time a value is referenced.
_cache = None
def __init__(self):
self.interp = Context()
self._globals = {}
self._cache = {}
# Install js proxy logic
self.interp.add_callable("proxy_get", self.proxy_get)
self.interp.add_callable("proxy_set", self.proxy_set)
self.interp.eval("""
var handler = {
get(target, property) {
rv = proxy_get(target.path, property)
if (typeof rv == 'string' && rv.substr(0, 5) == 'eval:') {
eval(rv.substr(5));
return eval(rv.substr(5));
}
return rv
},
set(target, property, value) {
return proxy_set(target.path, property, value)
}
}
var mk_proxy = function(path) {
return new Proxy({path: path}, handler);
}
""")
def set(self, **kwargs):
for (k, v) in kwargs.items():
self.interp.set(k, v)
def __call__(self, s):
return self.interp.eval(s)
# -----------------------------------------------------------------
def to_non_proxied(self, v):
# returns True/False and a value if the value can be represented
# by a Javascript type (not proxied)
if v in [None, True, False]:
return True, v
if type(v) in [QuickJSObject, str, int, float]:
return True, v
if type(v) in [UUID]:
return True, str(v)
return False, None
def to_eval_str(self, v, path=None):
# The value will be produced via eval if it is a string starting with eval:
# Cache results
if id(v) and id(v) in self._cache:
return self._cache[id(v)]
# If the value is a list, create a list of return values. Problem is
# that these have no path in the self._globals dict. They will have to
# be duplicated if they are objects.
# BUG here - every reference to the list, create another copy - need to cache
if type(v) == list:
rv = []
for v1 in v:
can_non_proxy, non_proxied = self.to_non_proxied(v1)
if can_non_proxy:
self._incr += 1
self.interp.set("_lv%s" % self._incr, v1)
rv.append("_lv%s" % self._incr)
else:
rv.append(self.to_eval_str(v1))
rv = "[" + ",".join(rv) + "]"
self._cache[id(v)] = rv
return rv
if type(v) == date:
rv = "new Date(%s, %s, %s)" % (v.year, v.month-1, v.day)
self._cache[id(v)] = rv
return rv
if type(v) == datetime:
rv = "new Date('%s')" % v.isoformat()
self._cache[id(v)] = rv
return rv
# this creates a function, which can never be garbage collected
if callable(v):
self._incr += 1
gname = "_fn%s" % self._incr
self.interp.add_callable(gname, v)
rv = "%s" % gname
self._cache[id(v)] = rv
return rv
# Anonymous variables are created by values inside lists
if path is None:
self._incr += 1
path = "_anon%s" % self._incr
self._globals[path] = v
# I need to do this for objects and try getattr
if type(v) == dict:
rv = "mk_proxy('%s')" % path
self._cache[id(v)] = rv
return rv
# Should be a user defined object to get here. Proxy it.
rv = "mk_proxy('%s')" % path
self._cache[id(v)] = rv
return rv
# -----------------------------------------------------------------
# Proxy Callback Points
def proxy_variable(self, **kwargs):
for (k, v) in kwargs.items():
self._globals[k] = v
self.interp.set(k, None)
js("""%s = mk_proxy("%s");""" % (k, k))
def eval_path(self, path):
parts = path.split(".")
root = self._globals
for part in parts:
root = root[part]
return root
def proxy_get(self, path, property):
# print(path, property)
root = self.eval_path(path)
try:
rv = root.get(property, None)
except:
# Object
rv = getattr(root, property)
# print(path, property, rv)
can_non_proxy, non_proxied = self.to_non_proxied(rv)
if can_non_proxy:
return rv
new_path = path + "." + property
estr = self.to_eval_str(rv, path=new_path)
# print("eval:" + estr)
return "eval:" + estr
def proxy_set(self, path, property, value):
# print(path, property, value)
root = self.eval_path(path)
root[property] = value
I am posting this piece of code as it might be of interest to other people following the same route. I cannot evolve it into a contribution, because there is too much complication in getting arrays working.
I used javascript Proxy to route requests to Python objects.
`""" Very Limited integration of data with QuickJS.
""" from quickjs import Context, Object as QuickJSObject import json from pprint import pp from uuid import UUID from datetime import date, datetime
class JS: interp = None
---------------------------------------------------------------------------
if name == 'main':
JS interpreter which can access python data via a proxy.
`