/
PythonRunner.js
129 lines (110 loc) · 3.15 KB
/
PythonRunner.js
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
123
124
125
126
127
128
129
import { EOL, platform } from 'os'
import { delimiter, join, relative, resolve } from 'path'
import { spawn } from 'child_process'
import extend from 'extend'
import readline from 'readline'
const { parse, stringify } = JSON
const { cwd } = process
const { has } = Reflect
export default class PythonRunner {
#env = null
#handlerName = null
#handlerPath = null
#runtime = null
constructor(funOptions, env) {
const { handlerName, handlerPath, runtime } = funOptions
this.#env = env
this.#handlerName = handlerName
this.#handlerPath = handlerPath
this.#runtime = platform() === 'win32' ? 'python.exe' : runtime
if (process.env.VIRTUAL_ENV) {
const runtimeDir = platform() === 'win32' ? 'Scripts' : 'bin'
process.env.PATH = [
join(process.env.VIRTUAL_ENV, runtimeDir),
delimiter,
process.env.PATH,
].join('')
}
const [pythonExecutable] = this.#runtime.split('.')
this.handlerProcess = spawn(
pythonExecutable,
[
'-u',
resolve(__dirname, 'invoke.py'),
relative(cwd(), this.#handlerPath),
this.#handlerName,
],
{
env: extend(process.env, this.#env),
shell: true,
},
)
this.handlerProcess.stdout.readline = readline.createInterface({
input: this.handlerProcess.stdout,
})
}
// () => void
cleanup() {
this.handlerProcess.kill()
}
_parsePayload(value) {
let payload
for (const item of value.split(EOL)) {
let json
// first check if it's JSON
try {
json = parse(item)
// nope, it's not JSON
} catch (err) {
// no-op
}
// now let's see if we have a property __offline_payload__
if (
json &&
typeof json === 'object' &&
has(json, '__offline_payload__')
) {
payload = json.__offline_payload__
// everything else is print(), logging, ...
} else {
console.log(item)
}
}
return payload
}
// invokeLocalPython, loosely based on:
// https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/invokeLocal/index.js#L410
// invoke.py, based on:
// https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/invokeLocal/invoke.py
async run(event, context) {
return new Promise((accept, reject) => {
const input = stringify({
context,
event,
})
const onErr = (data) => {
// TODO
console.log(data.toString())
}
const onLine = (line) => {
try {
const parsed = this._parsePayload(line.toString())
if (parsed) {
this.handlerProcess.stdout.readline.removeListener('line', onLine)
this.handlerProcess.stderr.removeListener('data', onErr)
return accept(parsed)
}
return null
} catch (err) {
return reject(err)
}
}
this.handlerProcess.stdout.readline.on('line', onLine)
this.handlerProcess.stderr.on('data', onErr)
process.nextTick(() => {
this.handlerProcess.stdin.write(input)
this.handlerProcess.stdin.write('\n')
})
})
}
}