-
Notifications
You must be signed in to change notification settings - Fork 30
/
node_python_bridge.py
122 lines (104 loc) · 3.66 KB
/
node_python_bridge.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from __future__ import unicode_literals
from codeop import Compile
import os
import sys
import json
import traceback
import platform
import struct
import math
NODE_CHANNEL_FD = int(os.environ['NODE_CHANNEL_FD'])
UNICODE_TYPE = unicode if sys.version_info[0] == 2 else str
if sys.version_info[0] <= 2:
# print('PY2')
def _exec(_code_, _globs_):
exec('exec _code_ in _globs_')
else:
_exec = getattr(__builtins__, 'exec')
_locals = {'__name__': '__console__', '__doc__': None}
_compile = Compile()
if platform.system() == 'Windows':
# hacky reimplementation of https://github.com/nodejs/node/blob/master/deps/uv/src/win/pipe.c
def read_data(f):
header = f.read(16)
if not header:
return header
try:
msg_length, = struct.unpack('<Q', header[8:])
return f.read(msg_length)
except:
raise ValueError('Error parsing msg with header: {}'.format(repr(header)))
def write_data(f, data):
header = struct.pack('<Q', 1) + struct.pack('<Q', len(data))
f.write(header + data)
f.flush()
else:
def read_data(f):
return reader.readline()
def write_data(f, data):
f.write(data)
f.flush()
def format_exception(t=None, e=None, tb=None):
return dict(
exception=dict(
type=dict(
name=t.__name__,
module=t.__module__
) if t else None,
message=str(e),
args=getattr(e, 'args', None),
format=traceback.format_exception_only(t, e)
) if e else None,
traceback=dict(
lineno=traceback.tb_lineno(tb) if hasattr(traceback, 'tb_lineno') else tb.tb_lineno,
strack=traceback.format_stack(tb.tb_frame),
format=traceback.format_tb(tb)
) if tb else None,
format=traceback.format_exception(t, e, tb)
)
class JavaScriptEncoder(json.JSONEncoder):
def default(self, o):
if math.isnan(o):
return 'NaN'
if math.isinf(o):
return 'Infinity' if o > 0 else '-Infinity'
return o.__dict__
if __name__ == '__main__':
writer = os.fdopen(NODE_CHANNEL_FD, 'wb')
reader = os.fdopen(NODE_CHANNEL_FD, 'rb')
while True:
try:
# Read new command
line = read_data(reader)
if not line:
break
try:
data = json.loads(line.decode('utf-8'))
except ValueError:
raise ValueError('Could not decode IPC data:\n{}'.format(repr(line)))
# Assert data saneness
if data['type'] not in ['execute', 'evaluate']:
raise Exception('Python bridge call `type` must be `execute` or `evaluate`')
if not isinstance(data['code'], UNICODE_TYPE):
raise Exception('Python bridge call `code` must be a string.')
# Run Python code
if data['type'] == 'execute':
_exec(_compile(data['code'], '<input>', 'exec'), _locals)
response = dict(type='success')
else:
value = eval(_compile(data['code'], '<input>', 'eval'), _locals)
response = dict(type='success', value=json.dumps(value, separators=(',', ':'), cls=JavaScriptEncoder))
except:
t, e, tb = sys.exc_info()
response = dict(type='exception', value=format_exception(t, e, tb))
data = json.dumps(response, separators=(',', ':')).encode('utf-8') + b'\n'
write_data(writer, data)
# Closing is messy
try:
reader.close()
except IOError:
pass
try:
writer.close()
except IOError:
pass