@primate/python 0.3.0 → 0.5.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 +42 -93
- package/lib/private/pyodide.d.ts +2 -1
- package/lib/private/pyodide.js +4 -1
- package/lib/private/to-request.d.ts +2 -2
- package/lib/private/to-response.d.ts +2 -2
- package/lib/private/to-response.js +2 -3
- package/lib/private/unwrap.d.ts +1 -1
- package/lib/private/wrapper.d.ts +5 -0
- package/lib/private/wrapper.js +98 -0
- package/lib/public/wrapper.d.ts +2 -0
- package/lib/public/wrapper.js +2 -0
- package/package.json +18 -15
package/lib/private/Default.js
CHANGED
|
@@ -1,107 +1,42 @@
|
|
|
1
1
|
import Runtime from "#Runtime";
|
|
2
|
-
import AppError from "@primate/core/AppError";
|
|
3
2
|
import TAG from "@primate/core/backend/TAG";
|
|
4
3
|
import fail from "@primate/core/fail";
|
|
5
4
|
import log from "@primate/core/log";
|
|
6
5
|
import assert from "@rcompat/assert";
|
|
7
|
-
import
|
|
8
|
-
import
|
|
6
|
+
import fs from "@rcompat/fs";
|
|
7
|
+
import io from "@rcompat/io";
|
|
9
8
|
const PACKAGE = "primate-run";
|
|
10
9
|
const [MAJOR, MINOR] = TAG.split(".").map(Number);
|
|
11
|
-
const wrapper = async (fileRef, packages) => {
|
|
12
|
-
const userPythonRaw = await fileRef.text();
|
|
13
|
-
const user_code = userPythonRaw.replace(/`/g, "\\`").replace(/\\/g, "\\\\");
|
|
14
|
-
return `
|
|
15
|
-
import route from "primate/route";
|
|
16
|
-
import to_request from "@primate/python/to-request";
|
|
17
|
-
import to_response from "@primate/python/to-response";
|
|
18
|
-
import session from "primate/config/session";
|
|
19
|
-
import helpers from "@primate/python/helpers";
|
|
20
|
-
import pyodide from "@primate/python/pyodide";
|
|
21
|
-
import borrow from "@primate/python/borrow";
|
|
22
|
-
|
|
23
|
-
const wrapped_session = {
|
|
24
|
-
get id() {
|
|
25
|
-
return session().id;
|
|
26
|
-
},
|
|
27
|
-
get exists() {
|
|
28
|
-
return session().exists;
|
|
29
|
-
},
|
|
30
|
-
create(initial) {
|
|
31
|
-
session().create(borrow(initial));
|
|
32
|
-
},
|
|
33
|
-
get() {
|
|
34
|
-
return session().get();
|
|
35
|
-
},
|
|
36
|
-
try() {
|
|
37
|
-
return session().get();
|
|
38
|
-
},
|
|
39
|
-
set(data) {
|
|
40
|
-
session().set(borrow(data));
|
|
41
|
-
},
|
|
42
|
-
destroy() {
|
|
43
|
-
session().destroy();
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const python = await pyodide();
|
|
48
|
-
const messageCallback = () => {};
|
|
49
|
-
await python.loadPackage("micropip", { messageCallback });
|
|
50
|
-
const micropip = python.pyimport("micropip");
|
|
51
|
-
await micropip.install("${PACKAGE}~=${TAG}.0", { messageCallback });
|
|
52
|
-
${packages.map(p => `await micropip.install("${p}", { messageCallback });`)
|
|
53
|
-
.join("\n")}
|
|
54
|
-
|
|
55
|
-
await python.runPython(\`${user_code}\`);
|
|
56
|
-
|
|
57
|
-
const registry = python.runPython("Route.registry()").toJs();
|
|
58
|
-
|
|
59
|
-
await python.runPython(\`
|
|
60
|
-
\${Object.keys(registry).map(route => \`
|
|
61
|
-
def run_\${route.toUpperCase()}(js_request, helpers_obj, session_obj):
|
|
62
|
-
Route.set_session(session_obj, helpers_obj)
|
|
63
|
-
request = Route.Request(js_request, helpers_obj)
|
|
64
|
-
return Route.call_route("\${route.toUpperCase()}", request)
|
|
65
|
-
\`).join("\\n")}
|
|
66
|
-
\`);
|
|
67
|
-
|
|
68
|
-
for (const [verb, func_name] of Object.entries(registry)) {
|
|
69
|
-
const route_fn = python.globals.get(\`run_\${verb.toUpperCase()}\`);
|
|
70
|
-
route[verb.toLowerCase()](async request => {
|
|
71
|
-
try {
|
|
72
|
-
const converted_request = await to_request(request);
|
|
73
|
-
const result = await route_fn(converted_request, helpers, wrapped_session);
|
|
74
|
-
return to_response(result);
|
|
75
|
-
} catch (e) {
|
|
76
|
-
console.error(\`python error (\${verb.toLowerCase()})\`, e);
|
|
77
|
-
return { status: 500, body: "Python execution error: " + e.message };
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
`;
|
|
82
|
-
};
|
|
83
10
|
function package_not_found() {
|
|
84
11
|
return fail("package not found - run 'pip install {0}~={1}.0'", PACKAGE, TAG);
|
|
85
12
|
}
|
|
86
13
|
function pkg_mismatch(major, minor) {
|
|
87
14
|
return fail("installed {0} package version {1}.{2}.x not in range ~> {3}.x", PACKAGE, major, minor, TAG);
|
|
88
15
|
}
|
|
16
|
+
async function show_package() {
|
|
17
|
+
const str0 = () => "";
|
|
18
|
+
let out = await io.run(`pip show ${PACKAGE} 2>/dev/null`).catch(str0);
|
|
19
|
+
if (out.trim())
|
|
20
|
+
return out;
|
|
21
|
+
out = await io.run(`uv pip show ${PACKAGE} 2>/dev/null`).catch(str0);
|
|
22
|
+
if (out.trim())
|
|
23
|
+
return out;
|
|
24
|
+
out = await io.run(`python -m pip show ${PACKAGE} 2>/dev/null`).catch(str0);
|
|
25
|
+
if (out.trim())
|
|
26
|
+
return out;
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
89
29
|
async function check_version() {
|
|
90
|
-
|
|
91
|
-
|
|
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;
|
|
30
|
+
const output = await show_package();
|
|
31
|
+
if (!output)
|
|
103
32
|
throw package_not_found();
|
|
104
|
-
|
|
33
|
+
const version_match = output.match(/Version:\s*(\d+)\.(\d+)\.(\d+)/);
|
|
34
|
+
if (!version_match)
|
|
35
|
+
throw package_not_found();
|
|
36
|
+
const [major, minor] = version_match.slice(1).map(Number);
|
|
37
|
+
if (major !== MAJOR || minor !== MINOR)
|
|
38
|
+
throw pkg_mismatch(major, minor);
|
|
39
|
+
log.info("using {0} package {1}.{2}.x", PACKAGE, major, minor);
|
|
105
40
|
}
|
|
106
41
|
export default class Default extends Runtime {
|
|
107
42
|
async build(app, next) {
|
|
@@ -109,15 +44,29 @@ export default class Default extends Runtime {
|
|
|
109
44
|
const requirements_txt = app.root.join("requirements.txt");
|
|
110
45
|
let packages = [];
|
|
111
46
|
if (await requirements_txt.exists()) {
|
|
112
|
-
const requirements = await
|
|
47
|
+
const requirements = await fs.text(requirements_txt);
|
|
113
48
|
packages = requirements
|
|
114
49
|
.split("\n")
|
|
115
50
|
.filter(line => line.trim() && !line.startsWith("#"))
|
|
116
51
|
.map(p => p.trim());
|
|
117
52
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
53
|
+
const packages_str = JSON.stringify(packages);
|
|
54
|
+
app.bind(this.fileExtension, async (file, { context }) => {
|
|
55
|
+
assert.true(context === "routes", "python: only route files are supported");
|
|
56
|
+
const relative = file.debase(app.path.routes).path.replace(/^\//, "");
|
|
57
|
+
const source = await file.text();
|
|
58
|
+
return `
|
|
59
|
+
import wrapper from "@primate/python/wrapper";
|
|
60
|
+
import i18n from "app:config:i18n";
|
|
61
|
+
import session from "app:config:session";
|
|
62
|
+
await wrapper(
|
|
63
|
+
${JSON.stringify(source)},
|
|
64
|
+
${packages_str},
|
|
65
|
+
"${PACKAGE}~=${TAG}.0",
|
|
66
|
+
"${relative}",
|
|
67
|
+
{ i18n, session }
|
|
68
|
+
);
|
|
69
|
+
`;
|
|
121
70
|
});
|
|
122
71
|
return next(app);
|
|
123
72
|
}
|
package/lib/private/pyodide.d.ts
CHANGED
package/lib/private/pyodide.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { loadPyodide } from "pyodide";
|
|
2
|
+
let pyodide = null;
|
|
2
3
|
export default async function () {
|
|
3
|
-
|
|
4
|
+
if (!pyodide)
|
|
5
|
+
pyodide = await loadPyodide({ indexURL: "./node_modules/pyodide" });
|
|
6
|
+
return pyodide;
|
|
4
7
|
}
|
|
5
8
|
//# sourceMappingURL=pyodide.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type RequestFacade from "@primate/core/request
|
|
2
|
-
import type Dict from "@rcompat/type
|
|
1
|
+
import type { RequestFacade } from "@primate/core/request";
|
|
2
|
+
import type { Dict } from "@rcompat/type";
|
|
3
3
|
type FileEntry = {
|
|
4
4
|
bytes: Uint8Array;
|
|
5
5
|
field: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type ResponseLike from "@primate/core/response
|
|
2
|
-
import type Dict from "@rcompat/type
|
|
1
|
+
import { type ResponseLike } from "@primate/core/response";
|
|
2
|
+
import type { Dict } from "@rcompat/type";
|
|
3
3
|
import type { PyProxy } from "pyodide/ffi";
|
|
4
4
|
declare const _default: (response: Dict | PyProxy) => ResponseLike;
|
|
5
5
|
export default _default;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import HANDLER_PROPERTY from "#handler-property";
|
|
2
2
|
import unwrap from "#unwrap";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import view from "@primate/core/response/view";
|
|
3
|
+
import response from "@primate/core/response";
|
|
4
|
+
const { error, redirect, view } = response;
|
|
6
5
|
const handlers = { error, redirect, view };
|
|
7
6
|
const handle_handler = (handler, response) => {
|
|
8
7
|
if (handler === "view") {
|
package/lib/private/unwrap.d.ts
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import borrow from "@primate/python/borrow";
|
|
2
|
+
import helpers from "@primate/python/helpers";
|
|
3
|
+
import pyodide from "@primate/python/pyodide";
|
|
4
|
+
import to_request from "@primate/python/to-request";
|
|
5
|
+
import to_response from "@primate/python/to-response";
|
|
6
|
+
import route from "primate/route";
|
|
7
|
+
const messageCallback = () => { };
|
|
8
|
+
let _micropip = null;
|
|
9
|
+
const installed = new Set();
|
|
10
|
+
async function load_micropip(python) {
|
|
11
|
+
if (!_micropip) {
|
|
12
|
+
_micropip = (async () => {
|
|
13
|
+
await python.loadPackage("micropip", { messageCallback });
|
|
14
|
+
return python.pyimport("micropip");
|
|
15
|
+
})();
|
|
16
|
+
}
|
|
17
|
+
return _micropip;
|
|
18
|
+
}
|
|
19
|
+
async function load_package(micropip, pkg) {
|
|
20
|
+
if (installed.has(pkg))
|
|
21
|
+
return;
|
|
22
|
+
await micropip.install(pkg, { messageCallback });
|
|
23
|
+
installed.add(pkg);
|
|
24
|
+
}
|
|
25
|
+
function wrap_session(session) {
|
|
26
|
+
if (!session)
|
|
27
|
+
return null;
|
|
28
|
+
return {
|
|
29
|
+
get id() { return session.id; },
|
|
30
|
+
get exists() { return session.exists; },
|
|
31
|
+
create(initial) { session.create(borrow(initial)); },
|
|
32
|
+
get() { return session.get(); },
|
|
33
|
+
try_get() { return session.try(); },
|
|
34
|
+
set(data) { session.set(borrow(data)); },
|
|
35
|
+
destroy() { session.destroy(); },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function wrap_i18n(i18n) {
|
|
39
|
+
if (!i18n)
|
|
40
|
+
return null;
|
|
41
|
+
return {
|
|
42
|
+
get locale() { return i18n.locale.get(); },
|
|
43
|
+
t(key, params) {
|
|
44
|
+
if (!params)
|
|
45
|
+
return i18n(key);
|
|
46
|
+
return i18n(key, JSON.parse(params));
|
|
47
|
+
},
|
|
48
|
+
set(locale) { i18n.locale.set(locale); },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export default async function wrapper(source, packages, primate_run, id, context = {}) {
|
|
52
|
+
const python = await pyodide();
|
|
53
|
+
const micropip = await load_micropip(python);
|
|
54
|
+
const route_id = JSON.stringify(id);
|
|
55
|
+
await load_package(micropip, primate_run);
|
|
56
|
+
for (const pkg of packages) {
|
|
57
|
+
await load_package(micropip, pkg);
|
|
58
|
+
}
|
|
59
|
+
await python.runPython(`
|
|
60
|
+
from primate import Route
|
|
61
|
+
Route.clear(${route_id})
|
|
62
|
+
Route.scope(${route_id})
|
|
63
|
+
`);
|
|
64
|
+
await python.runPython(source);
|
|
65
|
+
const verbs = python.runPython(`
|
|
66
|
+
from primate import Route
|
|
67
|
+
list(Route.registry(${route_id}).keys())
|
|
68
|
+
`).toJs();
|
|
69
|
+
const wrapped_session = wrap_session(context.session);
|
|
70
|
+
const wrapped_i18n = wrap_i18n(context.i18n);
|
|
71
|
+
for (const verb of verbs) {
|
|
72
|
+
route[verb.toLowerCase()](async (request) => {
|
|
73
|
+
python.globals.set("js_req", await to_request(request));
|
|
74
|
+
python.globals.set("helpers_obj", helpers);
|
|
75
|
+
python.globals.set("session_obj", wrapped_session);
|
|
76
|
+
python.globals.set("i18n_obj", wrapped_i18n);
|
|
77
|
+
const verb_str = JSON.stringify(verb);
|
|
78
|
+
try {
|
|
79
|
+
const result = await python.runPythonAsync(`
|
|
80
|
+
from primate import Route
|
|
81
|
+
await Route.call_js(${route_id}, ${verb_str}, js_req, helpers_obj, session_obj, i18n_obj)
|
|
82
|
+
`);
|
|
83
|
+
return to_response(result);
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
console.error(`python error (${verb.toLowerCase()})`, e);
|
|
87
|
+
return { status: 500, body: "Python execution error: " + e.message };
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
python.globals.delete("js_req");
|
|
91
|
+
python.globals.delete("helpers_obj");
|
|
92
|
+
python.globals.delete("session_obj");
|
|
93
|
+
python.globals.delete("i18n_obj");
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=wrapper.js.map
|
package/package.json
CHANGED
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primate/python",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Python backend for Primate",
|
|
5
5
|
"homepage": "https://primate.run/docs/backend/python",
|
|
6
6
|
"bugs": "https://github.com/primate-run/primate/issues",
|
|
7
|
+
"type": "module",
|
|
7
8
|
"license": "MIT",
|
|
8
|
-
"files": [
|
|
9
|
-
"/lib/**/*.js",
|
|
10
|
-
"/lib/**/*.d.ts",
|
|
11
|
-
"!/**/*.spec.*"
|
|
12
|
-
],
|
|
13
9
|
"repository": {
|
|
14
10
|
"type": "git",
|
|
15
11
|
"url": "https://github.com/primate-run/primate",
|
|
16
12
|
"directory": "packages/python"
|
|
17
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"/lib/**/*.js",
|
|
16
|
+
"/lib/**/*.d.ts",
|
|
17
|
+
"!/**/*.spec.*"
|
|
18
|
+
],
|
|
18
19
|
"dependencies": {
|
|
19
|
-
"@rcompat/assert": "^0.
|
|
20
|
-
"@rcompat/fs": "^0.
|
|
21
|
-
"@rcompat/
|
|
22
|
-
"pyodide": "^0.
|
|
23
|
-
"@primate/core": "^0.
|
|
24
|
-
"pema": "^0.
|
|
20
|
+
"@rcompat/assert": "^0.6.0",
|
|
21
|
+
"@rcompat/fs": "^0.25.2",
|
|
22
|
+
"@rcompat/io": "^0.3.0",
|
|
23
|
+
"pyodide": "^0.29.3",
|
|
24
|
+
"@primate/core": "^0.5.0",
|
|
25
|
+
"pema": "^0.5.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@rcompat/type": "^0.9.0"
|
|
25
29
|
},
|
|
26
30
|
"peerDependencies": {
|
|
27
|
-
"primate": "^0.
|
|
31
|
+
"primate": "^0.36.0"
|
|
28
32
|
},
|
|
29
|
-
"type": "module",
|
|
30
33
|
"imports": {
|
|
31
34
|
"#*": {
|
|
32
35
|
"apekit": "./src/private/*.ts",
|