pytwojs 1.0.0__py3-none-any.whl

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/__init__.py ADDED
@@ -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
pytwojs/_program.py ADDED
@@ -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>>>______]]]"
pytwojs/exceptions.py ADDED
@@ -0,0 +1,6 @@
1
+ class JSError(Exception):
2
+ pass
3
+
4
+
5
+ class JSException(JSError):
6
+ pass
pytwojs/interpreter.py ADDED
@@ -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']
pytwojs/utils.py ADDED
@@ -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,9 @@
1
+ pytwojs/__init__.py,sha256=qOhwd6-MsPv8HjVSoYT00Xo4ceyEerRcn9cYCurIXio,659
2
+ pytwojs/_program.py,sha256=i-NOLgPfdpYDlB72S_D2NaSTRlxTZRXeB9kaIOliQXE,1905
3
+ pytwojs/exceptions.py,sha256=EDZJ-HRop2I3u4jP6qFWI0KQjhN_0WxCQZPD9JCMiJE,80
4
+ pytwojs/interpreter.py,sha256=PkSQ7lyfBoydH8zVgzOZVBj5y6KL5GR8ximhSwK3P2s,5809
5
+ pytwojs/utils.py,sha256=Z9CXMicohjRpR8hmdYoJXxXOnmdfEPWG-Oau59ICgq8,314
6
+ pytwojs-1.0.0.dist-info/METADATA,sha256=0CRCf_F0SW1xbPXsHPG41reHp6ZVIz73igwpK6Zjri8,300
7
+ pytwojs-1.0.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
8
+ pytwojs-1.0.0.dist-info/top_level.txt,sha256=h1Jc5V_4aAsbVlzO4SIwZ_dEOFgxG4amrXCfYyHPDj0,8
9
+ pytwojs-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ pytwojs