Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pycjs, the python caller of js based on node_vm2 #1498

Merged
merged 4 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
786 changes: 786 additions & 0 deletions notebook/node_vm2_pycaller.ipynb

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions pycjs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python 3.9

# @Time : 2024/1/7 0:41
# @Author : 'Lou Zehua'
# @File : __init__.py.py

import os
import sys

if '__file__' not in globals():
# !pip install ipynbname # Remove comment symbols to solve the ModuleNotFoundError
import ipynbname

nb_path = ipynbname.path()
__file__ = str(nb_path)
cur_dir = os.path.dirname(__file__)
pkg_rootdir = os.path.dirname(cur_dir) # os.path.dirname()向上一级,注意要对应工程root路径
if pkg_rootdir not in sys.path: # 解决ipynb引用上层路径中的模块时的ModuleNotFoundError问题
sys.path.append(pkg_rootdir)
print('-- Add root directory "{}" to system path.'.format(pkg_rootdir))
60 changes: 60 additions & 0 deletions pycjs/open_digger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python 3.9

# @Time : 2024/1/14 10:26
# @Author : 'Lou Zehua'
# @File : pycjs_node_vm.py

import copy
import pandas as pd

from node_vm2 import NodeVM

from pycjs.pycjs_node_vm import get_export_module


vm_option_open_digger = {
# 'console': 'inherit',
# "wrapper": 'commonjs',
# 'sandbox': {},
'require': {
'external': '../lib',
# 'builtin': ['os'],
# 'import': ["ijavascript-plotly"],
'root': '../',
# 'mock': { }
}
}

js_import_open_digger = """
var openDigger = require('../src/open_digger.js');
"""

if __name__ == '__main__':
frank-zsy marked this conversation as resolved.
Show resolved Hide resolved
year = 2023
startMonth = 1
endMonth = 12
baseOptions = {
"startYear": year,
"endYear": year,
"startMonth": startMonth,
"endMonth": endMonth,
"groupTimeRange": 'year',
"order": 'DESC'
}

localOptions = {
"labelUnion": [':technology/database'],
"limit": 10
}

options = copy.deepcopy(baseOptions)
options.update(localOptions)

with NodeVM(**vm_option_open_digger) as vm:
# set show_indexes=True to display functions in the export module.
export_module = get_export_module(vm, js_import_open_digger, show_indexes=True, max_colwidth=60)
openDigger = export_module.openDigger
data_repo_openrank = openDigger.index.openrank.getRepoOpenrank(options)
print(pd.DataFrame(list(data_repo_openrank)))
188 changes: 188 additions & 0 deletions pycjs/pycjs_node_vm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python 3.9

# @Time : 2024/1/14 10:26
# @Author : 'Lou Zehua'
# @File : pycjs_node_vm.py

import re

import node_vm2
import pandas as pd

from node_vm2 import NodeVM


class Obj(object):
def __init__(self, d):
for k, v in d.items():
if isinstance(v, (list, tuple)):
setattr(self, k, [Obj(x) if isinstance(x, dict) else x for x in v])
else:
setattr(self, k, Obj(v) if isinstance(v, dict) else v)

getattr(self, k)

def __getattr__(self, name):
try:
return self.__getattribute__(name)
except AttributeError as e:
msg = str(e) + f" for {self}!"
raise AttributeError(msg)


class NodeVMFuncGen(object):
def __init__(self, func_name='_', vm_rt=None, *args, **kwargs):
self.func_name = func_name
self.vm_rt = vm_rt
self.args = args
self.kwargs = kwargs
self.func = self.func_impl
return

def func_impl(self, *args, **kwargs):
kwargs = kwargs or self.kwargs
vm_rt = kwargs.pop("vm_rt", None) or self.vm_rt
res = vm_rt.call_member(self.func_name, *args, **kwargs)
return res


vm_option = {
# 'console': 'inherit',
# "wrapper": 'commonjs',
# 'sandbox': {},
'require': {
'external': '../lib',
# 'builtin': ['os'],
# 'import': ["ijavascript-plotly"],
'root': '../',
# 'mock': { }
}
}


def apply_export_template(var_str, module_index_str):
return f"exports.{var_str} = {module_index_str};"


def formTree(path_nodes_list, leaf_node_list):
tree_root = {}
for path_nodes, leaf_node in zip(path_nodes_list, leaf_node_list):
curr_tree = tree_root
for i, key_node in enumerate(path_nodes):
if i >= len(path_nodes) - 1:
curr_tree[key_node] = leaf_node
break
if key_node not in curr_tree:
curr_tree[key_node] = {}
curr_tree = curr_tree[key_node]
return tree_root


def get_export_module(vm, js_code, module_name="", **kwargs):
if not module_name:
require_module_vars = re.findall(r"([_a-zA-Z][_0-9a-zA-Z]*)\s*=\s*require\(.+\);", js_code)
if len(require_module_vars):
module_name = require_module_vars[0]

if not module_name:
return None

js_export_indexes = """
function getObjElemIndex(obj, parent_path, res) {
for (let key in obj) {
child_path = parent_path + `.${key}`;
if (typeof obj[key] === 'object') {
getObjElemIndex(obj[key], child_path, res); // 递归调用自身处理子对象
} else {
res.push(child_path);
}
}
}
"""
js_export_indexes += """
var exportIndexes = [];
getObjElemIndex({module_name}, '{module_name}', exportIndexes);
exports.exportIndexes = exportIndexes;
""".format(module_name=module_name)

js_code += js_export_indexes
vm_rt = vm.run(js_code)
export_indexes = vm_rt.get_member("exportIndexes")
export_vars = [s.replace('.', '_') for s in export_indexes]

if kwargs.get("show_indexes", False):
df_export_module = pd.DataFrame({"export_vars": export_vars, "export_indexes": export_indexes})
pd.set_option('display.max_colwidth', kwargs.get("max_colwidth", 60))
columns = kwargs.get("columns", df_export_module.columns)
rows = kwargs.get("rows", [True]*df_export_module.shape[0])
apply_op = kwargs.get("apply_func", {"func": lambda x: x})
print(df_export_module[columns].loc[rows].apply(**apply_op))

# js export template: "exports.openDigger_label_getLabelData = openDigger.label.getLabelData;"
exports_statements = list(map(apply_export_template, export_vars, export_indexes))
exports_extend_str = '\n'.join(exports_statements)
js_open_digger_exports_extended = js_code + exports_extend_str
vm_rt = vm.run(js_open_digger_exports_extended)

export_indexes_split = [str(s).split('.') for s in export_indexes]
# Initialize vm_rt in the constructor
func_leaf_list = [NodeVMFuncGen(var_str, vm_rt=vm_rt).func for var_str in export_vars]
export_dict = formTree(export_indexes_split, func_leaf_list)
export_module = Obj(export_dict)
return export_module


if __name__ == '__main__':
from datetime import datetime

js = """
var os = require('os');
exports.test = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve("hello")
}, 3000);
});
};
exports.greet = name => console.log(`Hello ${name}!`);
"""

vm_option = {
# 'console': 'inherit',
# "wrapper": 'commonjs',
# 'sandbox': {},
'require': {
'external': '../lib',
'builtin': ['os'],
# 'import': ["ijavascript-plotly"],
'root': '../',
# 'mock': { }
}
}

with NodeVM(**vm_option) as vm:
try:
# set show_indexes=True to display functions in the export module.
pd.set_option('display.max_rows', None)
df_filter_op = {
"rows": range(40),
"columns": ["export_vars", "export_indexes"],
# "apply_func": {"func": lambda x: sum([len(x_elem) for x_elem in x]) < 25, "axis": 1}
}
export_module = get_export_module(vm, js, show_indexes=True, max_colwidth=60, **df_filter_op)
print(export_module)
os = export_module.os
print(os.hostname())
except node_vm2.VMError as e:
print(str(e))

vm_rt = vm.run(js)
test = NodeVMFuncGen("test", vm_rt=vm_rt).func
greet = NodeVMFuncGen("greet", vm_rt=vm_rt).func

print(datetime.now())
print(test())
print(datetime.now())
greet("John")
6 changes: 6 additions & 0 deletions pycjs/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ipynbname==2023.2.0.0
jupyterlab>=3.2.8
matplotlib>=3.5.3
node-vm2==0.4.7
numpy>=1.21.5
pandas>=1.4.4
77 changes: 77 additions & 0 deletions pycjs/vm_context_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python 3.9

# @Time : 2024/1/15 21:49
# @Author : 'Lou Zehua'
# @File : vm_context_manager.py

from node_vm2 import NodeVM


class VMContext(object):

def __init__(self, *args, **kwargs):
self.vm = NodeVM(*args, **kwargs) # This VM class can be any class with its context manager.

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
return self.vm.__exit__(exc_type, exc_val, exc_tb)

def open(self):
return self.vm.__enter__()

def close(self):
return self.destroy()

def destroy(self):
return self.vm.destroy() # return self.vm.__exit__(None, None, None) if exit func is not exist in the VM class.


if __name__ == '__main__':
year = 2023
startMonth = 1
endMonth = 12
options = {
"startYear": year,
"endYear": year,
"startMonth": startMonth,
"endMonth": endMonth,
"limit": 2
}
js_import_open_digger = """
var openDigger = require('../src/open_digger.js');
"""

vm_option_open_digger = {
# 'console': 'inherit',
# "wrapper": 'commonjs',
# 'sandbox': {},
'require': {
'external': '../lib',
# 'builtin': ['os'],
# 'import': ["ijavascript-plotly"],
'root': '../',
# 'mock': { }
}
}

from pycjs.pycjs_node_vm import get_export_module

# Context Manager for 'with' syntax of python code in py files
with VMContext(**vm_option_open_digger).open() as vm:
# set show_indexes=True to display functions in the export module.
export_module = get_export_module(vm, js_import_open_digger, show_indexes=False)
openDigger = export_module.openDigger
print(openDigger.index.openrank.getRepoOpenrank(options))

# Contex Manager for python codes with checkpoints in ipynb cells
vmc = VMContext(**vm_option_open_digger)
vmc.open()
vm = vmc.vm
export_module = get_export_module(vm, js_import_open_digger, show_indexes=False)
openDigger = export_module.openDigger
print(openDigger.index.openrank.getRepoOpenrank(options))
vmc.close()