pytwojs 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytwojs-1.0.0/PKG-INFO +11 -0
- pytwojs-1.0.0/pytwojs/__init__.py +26 -0
- pytwojs-1.0.0/pytwojs/_program.py +46 -0
- pytwojs-1.0.0/pytwojs/exceptions.py +6 -0
- pytwojs-1.0.0/pytwojs/interpreter.py +176 -0
- pytwojs-1.0.0/pytwojs/utils.py +14 -0
- pytwojs-1.0.0/pytwojs.egg-info/PKG-INFO +11 -0
- pytwojs-1.0.0/pytwojs.egg-info/SOURCES.txt +10 -0
- pytwojs-1.0.0/pytwojs.egg-info/dependency_links.txt +1 -0
- pytwojs-1.0.0/pytwojs.egg-info/top_level.txt +1 -0
- pytwojs-1.0.0/setup.cfg +4 -0
- pytwojs-1.0.0/setup.py +19 -0
pytwojs-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytwojs
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A high-performance Python library that relies on nodejs to execute JavaScript
|
|
5
|
+
Author: Samwe
|
|
6
|
+
Author-email: 1281722462@qq.com
|
|
7
|
+
Requires-Python: >=3.8.0
|
|
8
|
+
Dynamic: author
|
|
9
|
+
Dynamic: author-email
|
|
10
|
+
Dynamic: requires-python
|
|
11
|
+
Dynamic: summary
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
__package__ = 'pytwojs'
|
|
2
|
+
|
|
3
|
+
from typing import Union as _Union
|
|
4
|
+
from .interpreter import NodeInterpreter as _NodeInterpreter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Context(_NodeInterpreter):
|
|
8
|
+
|
|
9
|
+
def exec(self, code: _Union[str, bytes]):
|
|
10
|
+
if isinstance(code, str):
|
|
11
|
+
code = code.encode("utf-8")
|
|
12
|
+
super().exec(code)
|
|
13
|
+
|
|
14
|
+
def eval(self, code: _Union[str, bytes]):
|
|
15
|
+
if isinstance(code, str):
|
|
16
|
+
code = code.encode("utf-8")
|
|
17
|
+
return super().eval(code)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def compile_(code: _Union[str, bytes]) -> Context:
|
|
21
|
+
if isinstance(code, str):
|
|
22
|
+
code = code.encode("utf-8")
|
|
23
|
+
|
|
24
|
+
ctx = Context()
|
|
25
|
+
ctx.exec(code)
|
|
26
|
+
return ctx
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
PROGRAM = """
|
|
2
|
+
const repl = require('node:repl');
|
|
3
|
+
function toString(obj){return Object.prototype.toString.call(obj)}
|
|
4
|
+
function isClass(obj) {
|
|
5
|
+
if (typeof obj !== 'function') return false;
|
|
6
|
+
if (!obj.prototype) return false;
|
|
7
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, 'prototype');
|
|
8
|
+
if (descriptor?.writable !== false) return false;
|
|
9
|
+
let isCallable = false
|
|
10
|
+
try {
|
|
11
|
+
Reflect.construct(obj, [], obj);
|
|
12
|
+
isCallable = true
|
|
13
|
+
} catch (e) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const isNativeClass = Function.prototype.toString.call(obj).startsWith('class');
|
|
17
|
+
const isBuiltin = obj.name in globalThis;
|
|
18
|
+
return isNativeClass || isBuiltin || isCallable;
|
|
19
|
+
}
|
|
20
|
+
const replServer = repl.start({
|
|
21
|
+
prompt: '',
|
|
22
|
+
writer: (output) => {
|
|
23
|
+
switch (toString(output)) {
|
|
24
|
+
case "[object Undefined]":
|
|
25
|
+
return `{"stringify":"${JSON.stringify(output)}","toString":"${toString(output)}","exception":0}`;
|
|
26
|
+
case "[object Error]":
|
|
27
|
+
return `{"stringify":${JSON.stringify(output)},"toString":"${output.toString()}","exception":1}`;
|
|
28
|
+
case "[object Function]":
|
|
29
|
+
if (!isClass(output))
|
|
30
|
+
return `{"stringify":"${JSON.stringify(output)}","toString":"[Function: ${output.name}]","exception":0}`;
|
|
31
|
+
return `{"stringify":"${JSON.stringify(output)}","toString":"[class ${output.name}]","exception":0}`;
|
|
32
|
+
case "[object AsyncFunction]":
|
|
33
|
+
return `{"stringify":"${JSON.stringify(output)}","toString":"[AsyncFunction: ${output.name}]","exception":0}`;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return `{"stringify":${JSON.stringify(output)},"toString":"${output.toString()}","exception":0}`;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return `{"stringify":"${toString(output)}","toString":"${toString(output)}","exception":0}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
FLAGS = "-e"
|
|
45
|
+
EXEC_END_FLAGS = b"'[[[______<<<exec_done>>>______]]]'\n"
|
|
46
|
+
EXEC_DONE_FLAGS = "[[[______<<<exec_done>>>______]]]"
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
__package__ = "pytwojs"
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import subprocess
|
|
6
|
+
import typing
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
import weakref
|
|
11
|
+
|
|
12
|
+
from .utils import make_cmd
|
|
13
|
+
from .exceptions import JSException
|
|
14
|
+
from ._program import EXEC_END_FLAGS, EXEC_DONE_FLAGS
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _generate_name() -> str:
|
|
18
|
+
return f"__{str(int(time.time() * 1000000))}{uuid.uuid4().hex[:16]}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _close(node) -> None:
|
|
22
|
+
return_code = node.poll()
|
|
23
|
+
if return_code is None:
|
|
24
|
+
node.stdin.close()
|
|
25
|
+
node.stdout.close()
|
|
26
|
+
node.wait()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class NodeInterpreter:
|
|
30
|
+
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
cmd = make_cmd()
|
|
33
|
+
self._node = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
34
|
+
self._global_interpreter_lock = threading.Lock()
|
|
35
|
+
self._finalizer = weakref.finalize(self, _close, self._node)
|
|
36
|
+
|
|
37
|
+
def exec(self, code: bytes) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Execute the given JavaScript code(multi line)
|
|
40
|
+
:param code: bytes
|
|
41
|
+
:return: None
|
|
42
|
+
"""
|
|
43
|
+
with self._global_interpreter_lock:
|
|
44
|
+
self._write(code + b"\n")
|
|
45
|
+
self._flush()
|
|
46
|
+
self._write(EXEC_END_FLAGS)
|
|
47
|
+
self._flush()
|
|
48
|
+
while out := self._readline():
|
|
49
|
+
try:
|
|
50
|
+
out_obj = json.loads(out)
|
|
51
|
+
if isinstance(out_obj, dict):
|
|
52
|
+
if out_obj.get("stringify") == EXEC_DONE_FLAGS:
|
|
53
|
+
break
|
|
54
|
+
except json.JSONDecodeError:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
def eval(self, code: bytes) -> JSProxyWrapper:
|
|
58
|
+
"""
|
|
59
|
+
Execute single line JS expression or Get JavaScript object
|
|
60
|
+
:param code: JavaScript single line expression
|
|
61
|
+
:return: JSProxyWrapper
|
|
62
|
+
"""
|
|
63
|
+
with self._global_interpreter_lock:
|
|
64
|
+
self._write(code + b"\n")
|
|
65
|
+
self._flush()
|
|
66
|
+
result = self._reach_result_line()
|
|
67
|
+
|
|
68
|
+
if result[:8] == b"Uncaught":
|
|
69
|
+
py_obj = json.loads(result.removeprefix(b"Uncaught").strip())
|
|
70
|
+
raise JSException(py_obj['toString'])
|
|
71
|
+
|
|
72
|
+
return JSProxyWrapper(self, result, code.strip())
|
|
73
|
+
|
|
74
|
+
def _reach_result_line(self) -> bytes:
|
|
75
|
+
while out := self._readline():
|
|
76
|
+
out_cpy = out.strip()
|
|
77
|
+
if out_cpy[:8] == b"Uncaught":
|
|
78
|
+
return out_cpy
|
|
79
|
+
try:
|
|
80
|
+
pyobj = json.loads(out_cpy)
|
|
81
|
+
if isinstance(pyobj, dict):
|
|
82
|
+
if len(pyobj.keys()) == 3:
|
|
83
|
+
try:
|
|
84
|
+
_ = pyobj['stringify'] and pyobj['toString'] and pyobj['exception']
|
|
85
|
+
return out_cpy
|
|
86
|
+
except KeyError:
|
|
87
|
+
pass
|
|
88
|
+
except json.JSONDecodeError:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def is_closed(self):
|
|
93
|
+
return_code = self._node.poll()
|
|
94
|
+
if return_code is not None:
|
|
95
|
+
return True
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
def close(self) -> None:
|
|
99
|
+
self._finalizer()
|
|
100
|
+
|
|
101
|
+
def _write(self, code: bytes):
|
|
102
|
+
self._node.stdin.write(code)
|
|
103
|
+
|
|
104
|
+
def _flush(self):
|
|
105
|
+
self._node.stdin.flush()
|
|
106
|
+
|
|
107
|
+
def _readline(self):
|
|
108
|
+
return self._node.stdout.readline()
|
|
109
|
+
|
|
110
|
+
def __enter__(self) -> NodeInterpreter:
|
|
111
|
+
return self
|
|
112
|
+
|
|
113
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
114
|
+
self.close()
|
|
115
|
+
|
|
116
|
+
def __del__(self):
|
|
117
|
+
self.close()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class JSProxyWrapper:
|
|
121
|
+
|
|
122
|
+
def __init__(self, node: NodeInterpreter, js_result: bytes, name: bytes) -> None:
|
|
123
|
+
self._node_repl = node
|
|
124
|
+
self._js_result = js_result
|
|
125
|
+
self._name = name
|
|
126
|
+
self._lock = node._global_interpreter_lock
|
|
127
|
+
|
|
128
|
+
def _eval(self, code: bytes, is_obj=False) -> JSProxyWrapper:
|
|
129
|
+
with self._lock:
|
|
130
|
+
if not is_obj:
|
|
131
|
+
self._node_repl._write(self._name + b"." + code + b"\n")
|
|
132
|
+
else:
|
|
133
|
+
name = _generate_name().encode("utf-8")
|
|
134
|
+
self._node_repl._write(name + b"=" + code + b"\n")
|
|
135
|
+
self._node_repl._flush()
|
|
136
|
+
result = self._node_repl._reach_result_line()
|
|
137
|
+
|
|
138
|
+
if result[:8] == b"Uncaught":
|
|
139
|
+
pyobj = json.loads(result.removeprefix(b"Uncaught").strip())
|
|
140
|
+
raise JSException(pyobj['toString'])
|
|
141
|
+
|
|
142
|
+
if not is_obj:
|
|
143
|
+
return JSProxyWrapper(self._node_repl, result, self._name + b"." + code.strip())
|
|
144
|
+
return JSProxyWrapper(self._node_repl, result, name)
|
|
145
|
+
|
|
146
|
+
def call(self, func: str, *args) -> JSProxyWrapper:
|
|
147
|
+
"""Call functions within the object itself"""
|
|
148
|
+
call_js_str = f"{func}(...{json.dumps(args)})".encode("utf-8")
|
|
149
|
+
return self._eval(call_js_str)
|
|
150
|
+
|
|
151
|
+
def __getattr__(self, name) -> JSProxyWrapper:
|
|
152
|
+
return self._eval(name.encode('utf-8'))
|
|
153
|
+
|
|
154
|
+
def __call__(self, *args, **kwargs) -> JSProxyWrapper:
|
|
155
|
+
pyobj = json.loads(self._js_result)
|
|
156
|
+
if pyobj['toString'][1:14] == "AsyncFunction":
|
|
157
|
+
call_js_str = f"(await {self._name.decode('utf-8')}(...{json.dumps(args)}))".encode("utf-8")
|
|
158
|
+
elif pyobj['toString'][1:6] == "class":
|
|
159
|
+
call_js_str = f"new {self._name.decode('utf-8')}(...{json.dumps(args)})".encode("utf-8")
|
|
160
|
+
else:
|
|
161
|
+
call_js_str = f"{self._name.decode('utf-8')}(...{json.dumps(args)})".encode("utf-8")
|
|
162
|
+
|
|
163
|
+
return self._eval(call_js_str, True)
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def _value(self) -> bytes:
|
|
167
|
+
return self._js_result
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def pyobj(self) -> typing.Any:
|
|
171
|
+
"""Get python object"""
|
|
172
|
+
result = json.loads(self._js_result)
|
|
173
|
+
if result['stringify'] == "undefined":
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
return result['stringify']
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from ._program import PROGRAM, FLAGS
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_node_env():
|
|
6
|
+
node = os.environ.get('NODE_PATH') if os.environ.get('NODE_PATH') else os.environ.get('NODE')
|
|
7
|
+
if not node:
|
|
8
|
+
return "node"
|
|
9
|
+
return node
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def make_cmd():
|
|
13
|
+
node = get_node_env()
|
|
14
|
+
return [node, FLAGS, PROGRAM]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytwojs
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A high-performance Python library that relies on nodejs to execute JavaScript
|
|
5
|
+
Author: Samwe
|
|
6
|
+
Author-email: 1281722462@qq.com
|
|
7
|
+
Requires-Python: >=3.8.0
|
|
8
|
+
Dynamic: author
|
|
9
|
+
Dynamic: author-email
|
|
10
|
+
Dynamic: requires-python
|
|
11
|
+
Dynamic: summary
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pytwojs
|
pytwojs-1.0.0/setup.cfg
ADDED
pytwojs-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
EMAIL = '1281722462@qq.com'
|
|
5
|
+
AUTHOR = 'Samwe'
|
|
6
|
+
REQUIRES_PYTHON = '>=3.8.0'
|
|
7
|
+
DESCRIPTION = 'A high-performance Python library that relies on nodejs to execute JavaScript'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
setup(
|
|
11
|
+
name="pytwojs",
|
|
12
|
+
version="1.0.0",
|
|
13
|
+
packages=find_packages(),
|
|
14
|
+
author=AUTHOR,
|
|
15
|
+
author_email=EMAIL,
|
|
16
|
+
python_requires=REQUIRES_PYTHON,
|
|
17
|
+
description=DESCRIPTION
|
|
18
|
+
)
|
|
19
|
+
|