pyevaljs4 0.1.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.
pyevaljs4/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ __package__ = 'pyevaljs4'
2
+
3
+ from .pyevaljs import RunTime, JSException, RunTimeNotFoundException
4
+ from .__version__ import version
5
+
6
+
7
+ def compile_(js_code: str = None, mode: str = None):
8
+ """
9
+ Compile js code
10
+ :param js_code: js source code
11
+ :param mode: Execution mode, the default is '.js', meaning it will execute with '.js' behavior.
12
+ Other optional values include '.cjs' and '.mjs', etc.
13
+ :return: RunTime
14
+ """
15
+ if js_code is None:
16
+ js_code = ""
17
+
18
+ if mode is None:
19
+ mode = ".js"
20
+
21
+ return RunTime._compile(js_code, mode)
@@ -0,0 +1 @@
1
+ version = ".".join(map(str, (0, 1, 0)))
pyevaljs4/_program.py ADDED
@@ -0,0 +1,50 @@
1
+ NODE_PROGRAM = """
2
+ try {{
3
+ var __process = process;
4
+ }} catch (e) {{
5
+ console.log('[[<<exception>>]]' + JSON.stringify(e.stack));
6
+ }}
7
+ try {{
8
+ globalThis.require = require;
9
+ globalThis.exports = exports;
10
+ globalThis.module = module;
11
+ globalThis.__filename = __filename;
12
+ globalThis.__dirname = __dirname;
13
+ }} catch (e) {{}}
14
+ try {{
15
+ globalThis.eval({source});
16
+ }} catch (e) {{
17
+ console.log('[[<<exception>>]]' + JSON.stringify(e.stack));
18
+ }}
19
+ __process.stdin.setEncoding('utf8');
20
+ __process.stdin.on('data', function(data) {{
21
+ let input = data.trim();
22
+ try {{
23
+ if (input.substring(0, 24) === "[[PyEvalJS4_Async_Call]]"){{
24
+ globalThis.eval(input.substring(24))
25
+ }} else {{
26
+ var res = globalThis.eval(input)
27
+ console.log('[[<<result>>]]' + JSON.stringify(res))
28
+ }}
29
+ }} catch (e) {{
30
+ console.log('[[<<exception>>]]' + JSON.stringify(e.stack))
31
+ }}
32
+ }});
33
+ __process.on('uncaughtException', (err) => {{
34
+ console.log('[[<<exception>>]]' + err);
35
+ }});
36
+ """
37
+
38
+ ASYNC_EVAL = """
39
+ (async () => {{
40
+ try {{
41
+ let res = {};
42
+ console.log('[[<<result>>]]' + JSON.stringify(res));
43
+ }} catch (e) {{
44
+ console.log('[[<<exception>>]]' + JSON.stringify(e.stack))
45
+ }}
46
+ }})();
47
+ """
48
+
49
+ ASYNC_CALL = "{flags}{func}.apply(this, {args}).then(res => {{console.log('[[<<result>>]]' + JSON.stringify(res))}}, err => {{console.log('[[<<exception>>]]' + JSON.stringify(err.stack))}})"
50
+ SYNC_CALL = "{func}.apply(this, {args})"
@@ -0,0 +1,7 @@
1
+ class JSException(Exception):
2
+ pass
3
+
4
+
5
+ class RunTimeNotFoundException(Exception):
6
+ pass
7
+
pyevaljs4/pyevaljs.py ADDED
@@ -0,0 +1,145 @@
1
+ __package__ = 'pyevaljs4'
2
+
3
+ import logging
4
+ import json
5
+ import os
6
+ import subprocess
7
+ import tempfile
8
+ import weakref
9
+ import threading
10
+ from typing import Any
11
+ from functools import partial
12
+
13
+ from .exceptions import JSException, RunTimeNotFoundException
14
+ from ._program import NODE_PROGRAM, ASYNC_EVAL, ASYNC_CALL, SYNC_CALL
15
+ from .utils import get_node_env
16
+ from .settings import ASYNC_CALL_FLAGS
17
+
18
+ _logger = logging.getLogger('pyevaljs4')
19
+
20
+
21
+ class RunTime:
22
+
23
+ def __init__(self):
24
+ self._node = None
25
+ self._node_env = get_node_env()
26
+ self._path = None
27
+ self._initialize = False
28
+ self._finalizer = None
29
+ self._lock = threading.Lock()
30
+
31
+ def _init(self):
32
+ with self._lock:
33
+ if not self._initialize:
34
+ try:
35
+ self._node = subprocess.Popen(
36
+ [self._node_env, self._path],
37
+ stdin=subprocess.PIPE,
38
+ stdout=subprocess.PIPE,
39
+ stderr=subprocess.STDOUT,
40
+ universal_newlines=True,
41
+ encoding='utf-8'
42
+ )
43
+ except Exception as e:
44
+ os.remove(self._path)
45
+ raise RunTimeNotFoundException("RunTime(nodejs) not found error") from e
46
+
47
+ self._finalizer = weakref.finalize(self, self._close)
48
+ self._initialize = True
49
+
50
+ @classmethod
51
+ def _compile(cls, source: str, suffix: str):
52
+ self = cls()
53
+ fd, path = tempfile.mkstemp(suffix=suffix, dir='.')
54
+ with open(fd, 'w', encoding='utf-8') as fp:
55
+ fp.write(NODE_PROGRAM.format(source=json.dumps(source)))
56
+ self._path = path
57
+ self._init()
58
+ return self
59
+
60
+ def eval(self, code: str) -> Any:
61
+ if not code:
62
+ return
63
+
64
+ if code[:5] == 'await':
65
+ return self._async_eval(code)
66
+
67
+ return self._eval(code)
68
+
69
+ def _eval(self, code: str):
70
+ return self._execute(statement=code)
71
+
72
+ def _async_eval(self, code: str):
73
+ statement = ASYNC_CALL_FLAGS + ASYNC_EVAL.format(code)
74
+ return self._execute(statement)
75
+
76
+ def call(self, func: str, *args, arg_list: list = None, async_js_func=False) -> Any:
77
+ if arg_list is not None:
78
+ _args = arg_list
79
+ else:
80
+ _args = [arg for arg in args]
81
+
82
+ return self._call(func, _args, async_js_func)
83
+
84
+ def _call(self, func: str, args: list, async_js_func=False):
85
+ if async_js_func:
86
+ statement = ASYNC_CALL.format(flags=ASYNC_CALL_FLAGS, func=func, args=args)
87
+ else:
88
+ statement = SYNC_CALL.format(func=func, args=args)
89
+
90
+ return self._execute(statement=statement)
91
+
92
+ def _execute(self, statement: str):
93
+ with self._lock:
94
+ self._node.stdin.write(statement)
95
+ self._node.stdin.flush()
96
+ result = self._get_result()
97
+
98
+ if result["exception"]:
99
+ raise JSException(result['exception'])
100
+
101
+ return result['result']
102
+
103
+ def _get_result(self):
104
+ _result = {'result': None, 'exception': None}
105
+ while True:
106
+ out = self._node.stdout.readline()
107
+ if out[:14] == "[[<<result>>]]":
108
+ try:
109
+ result = json.loads(out[14:])
110
+ _result['result'] = result
111
+ except json.JSONDecodeError:
112
+ if out[14:].strip() != "undefined":
113
+ _logger.error("Not supported this behaviour")
114
+ _result['result'] = None
115
+ elif out[:17] == "[[<<exception>>]]":
116
+ _result['exception'] = out[17:]
117
+ else:
118
+ continue
119
+
120
+ return _result
121
+
122
+ def _close(self):
123
+ os.remove(self._path)
124
+ self._node.stdin.close()
125
+ self._node.stdout.close()
126
+ self._node.wait()
127
+
128
+ def close(self):
129
+ self._finalizer()
130
+
131
+ @property
132
+ def is_closed(self):
133
+ if self._finalizer is not None:
134
+ return not self._finalizer.alive
135
+
136
+ return None
137
+
138
+ def __enter__(self):
139
+ return self
140
+
141
+ def __exit__(self, exc_type, exc_val, exc_tb):
142
+ self.close()
143
+
144
+ def __getattr__(self, name):
145
+ return partial(self.call, name)
pyevaljs4/settings.py ADDED
@@ -0,0 +1 @@
1
+ ASYNC_CALL_FLAGS = '[[PyEvalJS4_Async_Call]]'
pyevaljs4/utils.py ADDED
@@ -0,0 +1,8 @@
1
+ import os
2
+
3
+
4
+ def get_node_env():
5
+ node = os.environ.get('NODE_PATH') if os.environ.get('NODE_PATH') else os.environ.get('NODE')
6
+ if not node:
7
+ return "node"
8
+ return node
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.2
2
+ Name: pyevaljs4
3
+ Version: 0.1.0
4
+ Summary: A high-performance Python library that relies on nodejs to execute JavaScript
5
+ Home-page: https://github.com/Smawexi/PyEvalJS4
6
+ Author: Samwe
7
+ Author-email: 1281722462@qq.com
8
+ Classifier: Programming Language :: Python :: 3.7
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: Implementation :: CPython
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Requires-Python: >=3.7.0
18
+ Description-Content-Type: text/markdown
19
+ Dynamic: author
20
+ Dynamic: author-email
21
+ Dynamic: classifier
22
+ Dynamic: description
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: requires-python
26
+ Dynamic: summary
27
+
28
+ ### A high-performance Python library that relies on nodejs to execute JavaScript
29
+
30
+ ---
31
+
32
+ ### Environments
33
+
34
+ **Please note that you need to install Node.js and set up the NODE_PATH or NODE environment variable properly!**
35
+ *If not set, the default will use the Node.js in the system's path.*
36
+
37
+ ---
38
+
39
+ ### Quickstart
40
+ ```python
41
+ import pyevaljs4
42
+
43
+ # example 1
44
+ rt = pyevaljs4.compile_("function f(a,b){console.log(a,b);return a+b;}")
45
+ res = rt.call('f', 'x', 'y')
46
+ print(res)
47
+ assert res == "xy"
48
+ # Another way of passing parameters
49
+ res = rt.call('f', arg_list=['x', 'y'])
50
+ print(res)
51
+ assert res == "xy"
52
+ # Other equivalent calling methods
53
+ print(rt.f('x', 'y'))
54
+ print(rt.f(arg_list=['x', 'y']))
55
+ # close runtime
56
+ rt.close()
57
+
58
+ # example 2
59
+ with pyevaljs4.compile_("function f(a,b){console.log(a,b);return a+b;}") as rt:
60
+ res = rt.call('f', 'x', 'y')
61
+ print(res)
62
+ assert res == "xy"
63
+
64
+ # example 3
65
+ # empty context
66
+ rt = pyevaljs4.compile_()
67
+ # Add the function f to this context
68
+ rt.eval("function f(a,b){console.log(a,b);return a+b;}")
69
+ res = rt.call('f', 'x', 'y')
70
+ print(res)
71
+ assert res == 'xy'
72
+ # Other calling methods
73
+ res = rt.eval("f('x', 'y')")
74
+ print(res)
75
+ assert res == "xy"
76
+ rt.close()
77
+
78
+ # example 4
79
+ # Call JS asynchronous function
80
+ rt = pyevaljs4.compile_()
81
+ rt.eval("async function f(a,b){console.log(a,b);return a+b;}")
82
+ # To call the JS asynchronous function, you need to pass async_js_func=True
83
+ res = rt.call('f', 'x', 'y', async_js_func=True)
84
+ print(res)
85
+ assert res == "xy"
86
+ res = rt.f('x', 'y', async_js_func=True)
87
+ print(res)
88
+ assert res == "xy"
89
+ # The shortcut for calling the JS asynchronous function can only be used this way.
90
+ # other cases are not supported.
91
+ res = rt.eval('await f("x", "y")')
92
+ print(res)
93
+ assert res == "xy"
94
+ rt.close()
95
+ ```
96
+ ---
97
+
98
+ ### Use a custom version of Node.js
99
+ - By setting environment variables to use a custom version of Node, you only need to set the NODE_PATH or NODE environment variables.
100
+
101
+ ```python
102
+ import os
103
+ # Highest priority
104
+ os.environ['NODE_PATH'] = '/path/to/node.exe'
105
+
106
+ # Second priority
107
+ os.environ['NODE'] = '/path/to/node.exe'
108
+ ```
@@ -0,0 +1,11 @@
1
+ pyevaljs4/__init__.py,sha256=S53UKqpi5i4pQ07cQDqpYJ76zhBNeNFbth2y7KKxaU0,599
2
+ pyevaljs4/__version__.py,sha256=_DohDc7EwJTJYV3QmbnMSYb2f2Ps7dx6vbq1aSw6I4A,41
3
+ pyevaljs4/_program.py,sha256=dAdUHV5XwsEDOZVRPxIxE7ba9eiIFLK5KxfUkq2XSC0,1557
4
+ pyevaljs4/exceptions.py,sha256=QdRE3XXje5s7kV_e9xTpDcQBHKv-NboUjX-MhKCOEik,101
5
+ pyevaljs4/pyevaljs.py,sha256=kPWWCZp4IYNEc35XuAmXu9sRqHzn5XV8amz95rvIrBI,4412
6
+ pyevaljs4/settings.py,sha256=DOqDt5qwfrqjX5uoWJNYzNr-BHhBfi_zDvvkJlXCO0M,47
7
+ pyevaljs4/utils.py,sha256=z8DxAwY-OxXGKFiiGFj4XBVR6ereqTS71d8gFZNVPL0,193
8
+ pyevaljs4-0.1.0.dist-info/METADATA,sha256=l_-WEaJ874uT74iLkGhgCn3GkqrfviY8XPxV9jlu2oc,3115
9
+ pyevaljs4-0.1.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
10
+ pyevaljs4-0.1.0.dist-info/top_level.txt,sha256=uf1xHqdnrTQuI-BxfuvFsZZaz7aR_Njc6MPdlQpNxYY,10
11
+ pyevaljs4-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ pyevaljs4