@primate/python 0.2.0 → 0.3.0
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.
- package/lib/private/Default.js +37 -12
- package/lib/private/to-request.d.ts +1 -1
- package/lib/private/to-request.js +24 -28
- package/package.json +5 -4
package/lib/private/Default.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import Runtime from "#Runtime";
|
|
2
|
-
import
|
|
2
|
+
import AppError from "@primate/core/AppError";
|
|
3
|
+
import TAG from "@primate/core/backend/TAG";
|
|
4
|
+
import fail from "@primate/core/fail";
|
|
5
|
+
import log from "@primate/core/log";
|
|
3
6
|
import assert from "@rcompat/assert";
|
|
4
7
|
import FileRef from "@rcompat/fs/FileRef";
|
|
5
|
-
|
|
8
|
+
import execute from "@rcompat/stdio/execute";
|
|
9
|
+
const PACKAGE = "primate-run";
|
|
10
|
+
const [MAJOR, MINOR] = TAG.split(".").map(Number);
|
|
11
|
+
const wrapper = async (fileRef, packages) => {
|
|
6
12
|
const userPythonRaw = await fileRef.text();
|
|
7
13
|
const user_code = userPythonRaw.replace(/`/g, "\\`").replace(/\\/g, "\\\\");
|
|
8
14
|
return `
|
|
@@ -40,18 +46,16 @@ const wrapped_session = {
|
|
|
40
46
|
|
|
41
47
|
const python = await pyodide();
|
|
42
48
|
const messageCallback = () => {};
|
|
43
|
-
|
|
44
49
|
await python.loadPackage("micropip", { messageCallback });
|
|
45
50
|
const micropip = python.pyimport("micropip");
|
|
46
|
-
await micropip.install("
|
|
47
|
-
${packages.map(p => `await micropip.install("${p}", { messageCallback });`)
|
|
51
|
+
await micropip.install("${PACKAGE}~=${TAG}.0", { messageCallback });
|
|
52
|
+
${packages.map(p => `await micropip.install("${p}", { messageCallback });`)
|
|
53
|
+
.join("\n")}
|
|
48
54
|
|
|
49
55
|
await python.runPython(\`${user_code}\`);
|
|
50
56
|
|
|
51
|
-
// Get the registry of registered route function names
|
|
52
57
|
const registry = python.runPython("Route.registry()").toJs();
|
|
53
58
|
|
|
54
|
-
// Create route handler functions
|
|
55
59
|
await python.runPython(\`
|
|
56
60
|
\${Object.keys(registry).map(route => \`
|
|
57
61
|
def run_\${route.toUpperCase()}(js_request, helpers_obj, session_obj):
|
|
@@ -61,10 +65,8 @@ def run_\${route.toUpperCase()}(js_request, helpers_obj, session_obj):
|
|
|
61
65
|
\`).join("\\n")}
|
|
62
66
|
\`);
|
|
63
67
|
|
|
64
|
-
// Create route handlers for each registered route
|
|
65
68
|
for (const [verb, func_name] of Object.entries(registry)) {
|
|
66
69
|
const route_fn = python.globals.get(\`run_\${verb.toUpperCase()}\`);
|
|
67
|
-
|
|
68
70
|
route[verb.toLowerCase()](async request => {
|
|
69
71
|
try {
|
|
70
72
|
const converted_request = await to_request(request);
|
|
@@ -78,8 +80,32 @@ for (const [verb, func_name] of Object.entries(registry)) {
|
|
|
78
80
|
}
|
|
79
81
|
`;
|
|
80
82
|
};
|
|
83
|
+
function package_not_found() {
|
|
84
|
+
return fail("package not found - run 'pip install {0}~={1}.0'", PACKAGE, TAG);
|
|
85
|
+
}
|
|
86
|
+
function pkg_mismatch(major, minor) {
|
|
87
|
+
return fail("installed {0} package version {1}.{2}.x not in range ~> {3}.x", PACKAGE, major, minor, TAG);
|
|
88
|
+
}
|
|
89
|
+
async function check_version() {
|
|
90
|
+
try {
|
|
91
|
+
const output = await execute(`pip show ${PACKAGE} 2>/dev/null`);
|
|
92
|
+
const version_match = output.match(/Version:\s*(\d+)\.(\d+)\.(\d+)/);
|
|
93
|
+
if (!version_match)
|
|
94
|
+
throw package_not_found();
|
|
95
|
+
const [, major, minor] = version_match.map(Number);
|
|
96
|
+
if (major !== MAJOR || minor !== MINOR)
|
|
97
|
+
throw pkg_mismatch(major, minor);
|
|
98
|
+
log.info("using {0} package {1}.{2}.x", PACKAGE, major, minor);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error instanceof AppError)
|
|
102
|
+
throw error;
|
|
103
|
+
throw package_not_found();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
81
106
|
export default class Default extends Runtime {
|
|
82
107
|
async build(app, next) {
|
|
108
|
+
await check_version();
|
|
83
109
|
const requirements_txt = app.root.join("requirements.txt");
|
|
84
110
|
let packages = [];
|
|
85
111
|
if (await requirements_txt.exists()) {
|
|
@@ -89,10 +115,9 @@ export default class Default extends Runtime {
|
|
|
89
115
|
.filter(line => line.trim() && !line.startsWith("#"))
|
|
90
116
|
.map(p => p.trim());
|
|
91
117
|
}
|
|
92
|
-
app.bind(this.fileExtension, async (route, {
|
|
118
|
+
app.bind(this.fileExtension, async (route, { context }) => {
|
|
93
119
|
assert(context === "routes", "python: only route files are supported");
|
|
94
|
-
|
|
95
|
-
await route.append(".js").write(code);
|
|
120
|
+
return await wrapper(route, packages);
|
|
96
121
|
});
|
|
97
122
|
return next(app);
|
|
98
123
|
}
|
|
@@ -24,7 +24,7 @@ export default function to_request(request: RequestFacade): Promise<{
|
|
|
24
24
|
body: {
|
|
25
25
|
text: () => string;
|
|
26
26
|
json: () => string;
|
|
27
|
-
|
|
27
|
+
form: () => Dict<string>;
|
|
28
28
|
files: () => FileEntry[];
|
|
29
29
|
binary: () => {
|
|
30
30
|
buffer: Uint8Array<ArrayBuffer>;
|
|
@@ -28,41 +28,37 @@ function toDict(obj) {
|
|
|
28
28
|
}
|
|
29
29
|
return out;
|
|
30
30
|
}
|
|
31
|
-
async function
|
|
32
|
-
const
|
|
33
|
-
const plain = Object.create(null);
|
|
31
|
+
async function bridge_form(body) {
|
|
32
|
+
const form = Object.create(null);
|
|
34
33
|
const files = [];
|
|
35
34
|
const pending = [];
|
|
36
|
-
for (const [k, v] of Object.entries(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
53
|
-
}));
|
|
54
|
-
}
|
|
35
|
+
for (const [k, v] of Object.entries(body.form())) {
|
|
36
|
+
form[k] = v;
|
|
37
|
+
}
|
|
38
|
+
for (const [k, v] of Object.entries(body.files())) {
|
|
39
|
+
const name = v.name;
|
|
40
|
+
const type = v.type;
|
|
41
|
+
const size = v.size;
|
|
42
|
+
pending.push(v.arrayBuffer().then(buffer => {
|
|
43
|
+
files.push({
|
|
44
|
+
bytes: new Uint8Array(buffer),
|
|
45
|
+
field: k,
|
|
46
|
+
name,
|
|
47
|
+
size,
|
|
48
|
+
type,
|
|
49
|
+
});
|
|
50
|
+
}));
|
|
55
51
|
}
|
|
56
52
|
await Promise.all(pending);
|
|
57
53
|
return {
|
|
58
|
-
|
|
54
|
+
form: () => form,
|
|
59
55
|
files: () => files,
|
|
60
56
|
};
|
|
61
57
|
}
|
|
62
58
|
async function bridgeBody(body) {
|
|
63
|
-
let
|
|
64
|
-
if (body.type === "
|
|
65
|
-
|
|
59
|
+
let form, buffer, blob;
|
|
60
|
+
if (body.type === "form") {
|
|
61
|
+
form = await bridge_form(body);
|
|
66
62
|
}
|
|
67
63
|
if (body.type === "binary") {
|
|
68
64
|
blob = body.binary();
|
|
@@ -73,8 +69,8 @@ async function bridgeBody(body) {
|
|
|
73
69
|
json: () => {
|
|
74
70
|
return JSON.stringify(body.json());
|
|
75
71
|
},
|
|
76
|
-
|
|
77
|
-
files: () =>
|
|
72
|
+
form: () => form.form(),
|
|
73
|
+
files: () => form.files(),
|
|
78
74
|
binary: () => {
|
|
79
75
|
const mime = blob.type || "application/octet-stream";
|
|
80
76
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primate/python",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Primate Python backend",
|
|
5
5
|
"homepage": "https://primate.run/docs/backend/python",
|
|
6
6
|
"bugs": "https://github.com/primate-run/primate/issues",
|
|
@@ -18,12 +18,13 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@rcompat/assert": "^0.3.1",
|
|
20
20
|
"@rcompat/fs": "^0.21.1",
|
|
21
|
+
"@rcompat/stdio": "^0.12.2",
|
|
21
22
|
"pyodide": "^0.28.3",
|
|
22
|
-
"@primate/core": "^0.
|
|
23
|
-
"pema": "^0.
|
|
23
|
+
"@primate/core": "^0.3.0",
|
|
24
|
+
"pema": "^0.3.0"
|
|
24
25
|
},
|
|
25
26
|
"peerDependencies": {
|
|
26
|
-
"primate": "^0.
|
|
27
|
+
"primate": "^0.34.0"
|
|
27
28
|
},
|
|
28
29
|
"type": "module",
|
|
29
30
|
"imports": {
|