@adhd/apigen-python-env 0.2.4 โ 0.2.6
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/CHANGELOG.md +20 -0
- package/index.js +1 -10
- package/index.mjs +205 -186
- package/package.json +12 -2
- package/python/apigen_python/flask_server.py +30 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 0.2.6 (2026-09-26)
|
|
2
|
+
|
|
3
|
+
### ๐ Features
|
|
4
|
+
|
|
5
|
+
- **vite-plugins:** absorb perf/test-resolve-fix โ test-time @adhd/* source resolution ([3e344506](https://github.com/PseudoSky/adhd/commit/3e344506))
|
|
6
|
+
|
|
7
|
+
### ๐ฉน Fixes
|
|
8
|
+
|
|
9
|
+
- **nx:** reconcile main's e2e lane with the flat ESLint config + fix missing-deps ([cf72a2ab](https://github.com/PseudoSky/adhd/commit/cf72a2ab))
|
|
10
|
+
- **vite:** restore import.meta.url in CJS output under vite 8 ([7916e639](https://github.com/PseudoSky/adhd/commit/7916e639))
|
|
11
|
+
- **nx:** finish the ESLint v9 flat-config migration and unblock the gate ([53f4ff3e](https://github.com/PseudoSky/adhd/commit/53f4ff3e))
|
|
12
|
+
|
|
13
|
+
### โค๏ธ Thank You
|
|
14
|
+
|
|
15
|
+
- pseudosky
|
|
16
|
+
|
|
17
|
+
## 0.2.5 (2026-09-24)
|
|
18
|
+
|
|
19
|
+
This was a version bump only for apigen-python-env to align it with other projects, there were no code changes.
|
|
20
|
+
|
|
1
21
|
## 0.2.4 (2026-08-08)
|
|
2
22
|
|
|
3
23
|
|
package/index.js
CHANGED
|
@@ -1,10 +1 @@
|
|
|
1
|
-
|
|
2
|
-
${j.stderr}`);const x=o.join(e,"build");r.existsSync(x)&&r.rmSync(x,{recursive:!0,force:!0});for(const f of r.readdirSync(e))f.endsWith(".egg-info")&&r.rmSync(o.join(e,f),{recursive:!0,force:!0});const S=r.mkdtempSync(o.join(b.tmpdir(),"apigen-python-wheel-"));try{const f=h.spawnSync(y,["-m","pip","wheel","--no-deps","--wheel-dir",S,e],{encoding:"utf8",timeout:6e5});if(f.status!==0)throw new Error(`apigen-python-env: pip wheel failed (exit ${f.status}):
|
|
3
|
-
${f.stderr}
|
|
4
|
-
stdout:
|
|
5
|
-
${f.stdout}`);const E=r.readdirSync(S).filter(A=>A.endsWith(".whl"));if(E.length===0)throw new Error("apigen-python-env: pip wheel produced no .whl file");const $=o.join(S,E[0]),N=d.length?`${$}[${d.join(",")}]`:$,g=h.spawnSync(y,["-m","pip","install","--quiet","--disable-pip-version-check",N],{encoding:"utf8",timeout:6e5});if(g.status!==0)throw new Error(`apigen-python-env: pip install "${N}" failed (exit ${g.status}):
|
|
6
|
-
${g.stderr}
|
|
7
|
-
stdout:
|
|
8
|
-
${g.stdout}`)}finally{r.rmSync(S,{recursive:!0,force:!0})}const T={pyprojectHash:u,extras:d};return r.writeFileSync(p,JSON.stringify(T)),{python:y,venvDir:c,pythonPkgDir:e}})}function L(t,e,n,i,a){return new Promise((c,p)=>{var v,w;const u=h.spawn(t,["-m","apigen_python.extractor",n,"--namespace",i,"--emit-json"],{cwd:e,env:{...process.env,PYTHONPATH:e},stdio:["ignore","pipe","pipe"]});let y="",l="";(v=u.stdout)==null||v.on("data",s=>{y+=s.toString("utf8")}),(w=u.stderr)==null||w.on("data",s=>{l+=s.toString("utf8")}),u.on("error",p),u.on("exit",s=>{if(s!==0){p(new Error(`${a}: extractor --emit-json exited with code ${s}:
|
|
9
|
-
${l}`));return}try{c(JSON.parse(y))}catch(d){p(new Error(`${a}: extractor --emit-json produced invalid JSON (${d.message}):
|
|
10
|
-
${y}`))}})})}exports.ensurePythonEnv=V;exports.resolvePythonPkgDir=k;exports.runExtractorEmitJson=L;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("node:fs");c=s(c);let l=require("node:os");l=s(l);let u=require("node:path");u=s(u);let d=require("node:crypto"),f=require("node:child_process");function p(e){let t=e??__dirname,n=u.resolve(t,`..`,`..`,`..`,`python`);if(c.existsSync(u.join(n,`apigen_python`)))return n;let r=u.join(t,`python`);if(c.existsSync(u.join(r,`apigen_python`)))return r;let i=t;for(let e=0;e<20;e++){let e=u.join(i,`packages`,`apigen`,`python`);if(c.existsSync(u.join(e,`apigen_python`)))return e;let t=u.dirname(i);if(t===i)break;i=t}return n}var m=[`python3.13`,`python3.12`,`python3.11`,`python3`],h=[3,11];function g(e){let t=(0,f.spawnSync)(e,[`-c`,`import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")`],{encoding:`utf8`,timeout:15e3});if(t.status!==0||!t.stdout)return;let[n,r]=t.stdout.trim().split(`.`).map(Number);if(Number.isFinite(n)&&Number.isFinite(r))return[n,r]}function _(){for(let e of m){let t=g(e);if(t&&(t[0]>h[0]||t[0]===h[0]&&t[1]>=h[1]))return e}throw Error(`apigen-python-env: no Python >= ${h.join(`.`)} found on PATH (tried: ${m.join(`, `)}). Install Python ${h.join(`.`)}+ or set APIGEN_PYTHON to a provisioned interpreter.`)}function v(e){return process.platform===`win32`?u.join(e,`Scripts`,`python.exe`):u.join(e,`bin`,`python3`)}function y(e){let t=c.readFileSync(u.join(e,`pyproject.toml`));return(0,d.createHash)(`sha256`).update(t).digest(`hex`)}function b(e){try{let t=JSON.parse(c.readFileSync(e,`utf8`));if(typeof t.pyprojectHash==`string`&&Array.isArray(t.extras))return t}catch{}}function x(e,t,n){let r=Date.now()+t;for(;;)try{c.mkdirSync(e,{recursive:!1});break}catch{if(Date.now()>r){try{c.rmdirSync(e)}catch{}continue}(0,f.spawnSync)(process.execPath,[`-e`,`setTimeout(()=>{}, 250)`],{timeout:5e3})}try{return n()}finally{try{c.rmdirSync(e)}catch{}}}function S(e={}){let t=p(),n=process.env.APIGEN_PYTHON;if(n){if(!c.existsSync(n))throw Error(`apigen-python-env: APIGEN_PYTHON points to a missing file: ${n}`);return{python:n,venvDir:``,pythonPkgDir:t}}let r=[...new Set(e.extras??[])].sort(),i=e.envHome??process.env.APIGEN_PYENV_HOME??u.join(l.homedir(),`.adhd`,`apigen`),a=u.join(i,`pyvenv`),o=u.join(a,`.apigen-stamp`),s=y(t),d=v(a),m=()=>{if(!c.existsSync(d))return!1;let e=b(o);return e?.pyprojectHash===s&&r.every(t=>e.extras.includes(t))};return m()?{python:d,venvDir:a,pythonPkgDir:t}:(c.mkdirSync(i,{recursive:!0}),x(u.join(i,`pyvenv.lock`),e.lockTimeoutMs??3e5,()=>{if(m())return{python:d,venvDir:a,pythonPkgDir:t};let e=b(o),n=[...new Set([...e?.extras??[],...r])].sort();c.rmSync(a,{recursive:!0,force:!0});let i=_(),p=(0,f.spawnSync)(i,[`-m`,`venv`,a],{encoding:`utf8`,timeout:12e4});if(p.status!==0)throw Error(`apigen-python-env: "${i} -m venv" failed (exit ${p.status}):\n${p.stderr}`);let h=u.join(t,`build`);c.existsSync(h)&&c.rmSync(h,{recursive:!0,force:!0});for(let e of c.readdirSync(t))e.endsWith(`.egg-info`)&&c.rmSync(u.join(t,e),{recursive:!0,force:!0});let g=c.mkdtempSync(u.join(l.tmpdir(),`apigen-python-wheel-`));try{let e=(0,f.spawnSync)(d,[`-m`,`pip`,`wheel`,`--no-deps`,`--wheel-dir`,g,t],{encoding:`utf8`,timeout:6e5});if(e.status!==0)throw Error(`apigen-python-env: pip wheel failed (exit ${e.status}):\n${e.stderr}\nstdout:\n${e.stdout}`);let r=c.readdirSync(g).filter(e=>e.endsWith(`.whl`));if(r.length===0)throw Error(`apigen-python-env: pip wheel produced no .whl file`);let i=u.join(g,r[0]),a=n.length?`${i}[${n.join(`,`)}]`:i,o=(0,f.spawnSync)(d,[`-m`,`pip`,`install`,`--quiet`,`--disable-pip-version-check`,a],{encoding:`utf8`,timeout:6e5});if(o.status!==0)throw Error(`apigen-python-env: pip install "${a}" failed (exit ${o.status}):\n${o.stderr}\nstdout:\n${o.stdout}`)}finally{c.rmSync(g,{recursive:!0,force:!0})}let v={pyprojectHash:s,extras:n};return c.writeFileSync(o,JSON.stringify(v)),{python:d,venvDir:a,pythonPkgDir:t}}))}function C(e,t,n,r,i){return new Promise((a,o)=>{let s=(0,f.spawn)(e,[`-m`,`apigen_python.extractor`,n,`--namespace`,r,`--emit-json`],{cwd:t,env:{...process.env,PYTHONPATH:t},stdio:[`ignore`,`pipe`,`pipe`]}),c=``,l=``;s.stdout?.on(`data`,e=>{c+=e.toString(`utf8`)}),s.stderr?.on(`data`,e=>{l+=e.toString(`utf8`)}),s.on(`error`,o),s.on(`exit`,e=>{if(e!==0){o(Error(`${i}: extractor --emit-json exited with code ${e}:\n${l}`));return}try{a(JSON.parse(c))}catch(e){o(Error(`${i}: extractor --emit-json produced invalid JSON (${e.message}):\n${c}`))}})})}exports.ensurePythonEnv=S,exports.resolvePythonPkgDir=p,exports.runExtractorEmitJson=C;
|
package/index.mjs
CHANGED
|
@@ -1,195 +1,214 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
4
|
-
import { createHash as
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
a = u;
|
|
22
|
-
}
|
|
23
|
-
return o;
|
|
1
|
+
import * as e from "node:fs";
|
|
2
|
+
import * as t from "node:os";
|
|
3
|
+
import * as n from "node:path";
|
|
4
|
+
import { createHash as r } from "node:crypto";
|
|
5
|
+
import { spawn as i, spawnSync as a } from "node:child_process";
|
|
6
|
+
//#region src/lib/python-env.ts
|
|
7
|
+
function o(t) {
|
|
8
|
+
let r = t ?? __dirname, i = n.resolve(r, "..", "..", "..", "python");
|
|
9
|
+
if (e.existsSync(n.join(i, "apigen_python"))) return i;
|
|
10
|
+
let a = n.join(r, "python");
|
|
11
|
+
if (e.existsSync(n.join(a, "apigen_python"))) return a;
|
|
12
|
+
let o = r;
|
|
13
|
+
for (let t = 0; t < 20; t++) {
|
|
14
|
+
let t = n.join(o, "packages", "apigen", "python");
|
|
15
|
+
if (e.existsSync(n.join(t, "apigen_python"))) return t;
|
|
16
|
+
let r = n.dirname(o);
|
|
17
|
+
if (r === o) break;
|
|
18
|
+
o = r;
|
|
19
|
+
}
|
|
20
|
+
return i;
|
|
24
21
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
var s = [
|
|
23
|
+
"python3.13",
|
|
24
|
+
"python3.12",
|
|
25
|
+
"python3.11",
|
|
26
|
+
"python3"
|
|
27
|
+
], c = [3, 11];
|
|
28
|
+
function l(e) {
|
|
29
|
+
let t = a(e, ["-c", "import sys; print(f\"{sys.version_info[0]}.{sys.version_info[1]}\")"], {
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
timeout: 15e3
|
|
32
|
+
});
|
|
33
|
+
if (t.status !== 0 || !t.stdout) return;
|
|
34
|
+
let [n, r] = t.stdout.trim().split(".").map(Number);
|
|
35
|
+
if (Number.isFinite(n) && Number.isFinite(r)) return [n, r];
|
|
36
36
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
throw new Error(
|
|
44
|
-
`apigen-python-env: no Python >= ${m.join(".")} found on PATH (tried: ${_.join(", ")}). Install Python ${m.join(".")}+ or set APIGEN_PYTHON to a provisioned interpreter.`
|
|
45
|
-
);
|
|
37
|
+
function u() {
|
|
38
|
+
for (let e of s) {
|
|
39
|
+
let t = l(e);
|
|
40
|
+
if (t && (t[0] > c[0] || t[0] === c[0] && t[1] >= c[1])) return e;
|
|
41
|
+
}
|
|
42
|
+
throw Error(`apigen-python-env: no Python >= ${c.join(".")} found on PATH (tried: ${s.join(", ")}). Install Python ${c.join(".")}+ or set APIGEN_PYTHON to a provisioned interpreter.`);
|
|
46
43
|
}
|
|
47
|
-
function
|
|
48
|
-
|
|
44
|
+
function d(e) {
|
|
45
|
+
return process.platform === "win32" ? n.join(e, "Scripts", "python.exe") : n.join(e, "bin", "python3");
|
|
49
46
|
}
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
function f(t) {
|
|
48
|
+
let i = e.readFileSync(n.join(t, "pyproject.toml"));
|
|
49
|
+
return r("sha256").update(i).digest("hex");
|
|
53
50
|
}
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
} catch {
|
|
60
|
-
}
|
|
51
|
+
function p(t) {
|
|
52
|
+
try {
|
|
53
|
+
let n = JSON.parse(e.readFileSync(t, "utf8"));
|
|
54
|
+
if (typeof n.pyprojectHash == "string" && Array.isArray(n.extras)) return n;
|
|
55
|
+
} catch {}
|
|
61
56
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
} catch {
|
|
84
|
-
}
|
|
85
|
-
}
|
|
57
|
+
function m(t, n, r) {
|
|
58
|
+
let i = Date.now() + n;
|
|
59
|
+
for (;;) try {
|
|
60
|
+
e.mkdirSync(t, { recursive: !1 });
|
|
61
|
+
break;
|
|
62
|
+
} catch {
|
|
63
|
+
if (Date.now() > i) {
|
|
64
|
+
try {
|
|
65
|
+
e.rmdirSync(t);
|
|
66
|
+
} catch {}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
a(process.execPath, ["-e", "setTimeout(()=>{}, 250)"], { timeout: 5e3 });
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
return r();
|
|
73
|
+
} finally {
|
|
74
|
+
try {
|
|
75
|
+
e.rmdirSync(t);
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
86
78
|
}
|
|
87
|
-
function
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
79
|
+
function h(r = {}) {
|
|
80
|
+
let i = o(), s = process.env.APIGEN_PYTHON;
|
|
81
|
+
if (s) {
|
|
82
|
+
if (!e.existsSync(s)) throw Error(`apigen-python-env: APIGEN_PYTHON points to a missing file: ${s}`);
|
|
83
|
+
return {
|
|
84
|
+
python: s,
|
|
85
|
+
venvDir: "",
|
|
86
|
+
pythonPkgDir: i
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
let c = [...new Set(r.extras ?? [])].sort(), l = r.envHome ?? process.env.APIGEN_PYENV_HOME ?? n.join(t.homedir(), ".adhd", "apigen"), h = n.join(l, "pyvenv"), g = n.join(h, ".apigen-stamp"), _ = f(i), v = d(h), y = () => {
|
|
90
|
+
if (!e.existsSync(v)) return !1;
|
|
91
|
+
let t = p(g);
|
|
92
|
+
return t?.pyprojectHash === _ && c.every((e) => t.extras.includes(e));
|
|
93
|
+
};
|
|
94
|
+
return y() ? {
|
|
95
|
+
python: v,
|
|
96
|
+
venvDir: h,
|
|
97
|
+
pythonPkgDir: i
|
|
98
|
+
} : (e.mkdirSync(l, { recursive: !0 }), m(n.join(l, "pyvenv.lock"), r.lockTimeoutMs ?? 3e5, () => {
|
|
99
|
+
if (y()) return {
|
|
100
|
+
python: v,
|
|
101
|
+
venvDir: h,
|
|
102
|
+
pythonPkgDir: i
|
|
103
|
+
};
|
|
104
|
+
let r = p(g), o = [.../* @__PURE__ */ new Set([...r?.extras ?? [], ...c])].sort();
|
|
105
|
+
e.rmSync(h, {
|
|
106
|
+
recursive: !0,
|
|
107
|
+
force: !0
|
|
108
|
+
});
|
|
109
|
+
let s = u(), l = a(s, [
|
|
110
|
+
"-m",
|
|
111
|
+
"venv",
|
|
112
|
+
h
|
|
113
|
+
], {
|
|
114
|
+
encoding: "utf8",
|
|
115
|
+
timeout: 12e4
|
|
116
|
+
});
|
|
117
|
+
if (l.status !== 0) throw Error(`apigen-python-env: "${s} -m venv" failed (exit ${l.status}):\n${l.stderr}`);
|
|
118
|
+
let d = n.join(i, "build");
|
|
119
|
+
e.existsSync(d) && e.rmSync(d, {
|
|
120
|
+
recursive: !0,
|
|
121
|
+
force: !0
|
|
122
|
+
});
|
|
123
|
+
for (let t of e.readdirSync(i)) t.endsWith(".egg-info") && e.rmSync(n.join(i, t), {
|
|
124
|
+
recursive: !0,
|
|
125
|
+
force: !0
|
|
126
|
+
});
|
|
127
|
+
let f = e.mkdtempSync(n.join(t.tmpdir(), "apigen-python-wheel-"));
|
|
128
|
+
try {
|
|
129
|
+
let t = a(v, [
|
|
130
|
+
"-m",
|
|
131
|
+
"pip",
|
|
132
|
+
"wheel",
|
|
133
|
+
"--no-deps",
|
|
134
|
+
"--wheel-dir",
|
|
135
|
+
f,
|
|
136
|
+
i
|
|
137
|
+
], {
|
|
138
|
+
encoding: "utf8",
|
|
139
|
+
timeout: 6e5
|
|
140
|
+
});
|
|
141
|
+
if (t.status !== 0) throw Error(`apigen-python-env: pip wheel failed (exit ${t.status}):\n${t.stderr}\nstdout:\n${t.stdout}`);
|
|
142
|
+
let r = e.readdirSync(f).filter((e) => e.endsWith(".whl"));
|
|
143
|
+
if (r.length === 0) throw Error("apigen-python-env: pip wheel produced no .whl file");
|
|
144
|
+
let s = n.join(f, r[0]), c = o.length ? `${s}[${o.join(",")}]` : s, l = a(v, [
|
|
145
|
+
"-m",
|
|
146
|
+
"pip",
|
|
147
|
+
"install",
|
|
148
|
+
"--quiet",
|
|
149
|
+
"--disable-pip-version-check",
|
|
150
|
+
c
|
|
151
|
+
], {
|
|
152
|
+
encoding: "utf8",
|
|
153
|
+
timeout: 6e5
|
|
154
|
+
});
|
|
155
|
+
if (l.status !== 0) throw Error(`apigen-python-env: pip install "${c}" failed (exit ${l.status}):\n${l.stderr}\nstdout:\n${l.stdout}`);
|
|
156
|
+
} finally {
|
|
157
|
+
e.rmSync(f, {
|
|
158
|
+
recursive: !0,
|
|
159
|
+
force: !0
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
let m = {
|
|
163
|
+
pyprojectHash: _,
|
|
164
|
+
extras: o
|
|
165
|
+
};
|
|
166
|
+
return e.writeFileSync(g, JSON.stringify(m)), {
|
|
167
|
+
python: v,
|
|
168
|
+
venvDir: h,
|
|
169
|
+
pythonPkgDir: i
|
|
170
|
+
};
|
|
171
|
+
}));
|
|
154
172
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
${
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/lib/run-extractor.ts
|
|
175
|
+
function g(e, t, n, r, a) {
|
|
176
|
+
return new Promise((o, s) => {
|
|
177
|
+
let c = i(e, [
|
|
178
|
+
"-m",
|
|
179
|
+
"apigen_python.extractor",
|
|
180
|
+
n,
|
|
181
|
+
"--namespace",
|
|
182
|
+
r,
|
|
183
|
+
"--emit-json"
|
|
184
|
+
], {
|
|
185
|
+
cwd: t,
|
|
186
|
+
env: {
|
|
187
|
+
...process.env,
|
|
188
|
+
PYTHONPATH: t
|
|
189
|
+
},
|
|
190
|
+
stdio: [
|
|
191
|
+
"ignore",
|
|
192
|
+
"pipe",
|
|
193
|
+
"pipe"
|
|
194
|
+
]
|
|
195
|
+
}), l = "", u = "";
|
|
196
|
+
c.stdout?.on("data", (e) => {
|
|
197
|
+
l += e.toString("utf8");
|
|
198
|
+
}), c.stderr?.on("data", (e) => {
|
|
199
|
+
u += e.toString("utf8");
|
|
200
|
+
}), c.on("error", s), c.on("exit", (e) => {
|
|
201
|
+
if (e !== 0) {
|
|
202
|
+
s(/* @__PURE__ */ Error(`${a}: extractor --emit-json exited with code ${e}:\n${u}`));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
o(JSON.parse(l));
|
|
207
|
+
} catch (e) {
|
|
208
|
+
s(/* @__PURE__ */ Error(`${a}: extractor --emit-json produced invalid JSON (${e.message}):\n${l}`));
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
});
|
|
190
212
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
O as resolvePythonPkgDir,
|
|
194
|
-
q as runExtractorEmitJson
|
|
195
|
-
};
|
|
213
|
+
//#endregion
|
|
214
|
+
export { h as ensurePythonEnv, o as resolvePythonPkgDir, g as runExtractorEmitJson };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhd/apigen-python-env",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"main": "./index.js",
|
|
5
5
|
"module": "./index.mjs",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -12,9 +12,19 @@
|
|
|
12
12
|
"provisioning"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@adhd/apigen-core-client": "^0.3.
|
|
15
|
+
"@adhd/apigen-core-client": "^0.3.2"
|
|
16
16
|
},
|
|
17
17
|
"publishConfig": {
|
|
18
18
|
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/PseudoSky/adhd.git",
|
|
24
|
+
"directory": "packages/apigen/python-env"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/PseudoSky/adhd/tree/main/packages/apigen/python-env#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/PseudoSky/adhd/issues"
|
|
19
29
|
}
|
|
20
30
|
}
|
|
@@ -434,11 +434,12 @@ def _resolve_batch_concurrency(mode: str, requested: Any, item_count: int) -> in
|
|
|
434
434
|
def _dispatch_batch(
|
|
435
435
|
state: _ServerState, body: dict[str, Any], envelope: dict[str, Any]
|
|
436
436
|
) -> list[dict[str, Any]]:
|
|
437
|
-
"""Fan out `body['items']` to the real operation
|
|
438
|
-
the Python host's OWN executor against
|
|
439
|
-
(LEFT column only; this function is
|
|
440
|
-
`invokeBatch`). Implements the
|
|
441
|
-
(`tmp/apigen-batch-python-design.md`
|
|
437
|
+
"""Fan out `body['input']['items']` to the real operation
|
|
438
|
+
`body['input']['operation']` โ the Python host's OWN executor against
|
|
439
|
+
BATCH_0.0.1.md ยง5's wire contract (LEFT column only; this function is
|
|
440
|
+
never a call into the TS `invokeBatch`). Implements the
|
|
441
|
+
architect-reviewed design's binding fixes (`tmp/apigen-batch-python-design.md`
|
|
442
|
+
ยง3.5):
|
|
442
443
|
|
|
443
444
|
- F2: `mode` branching โ `parallel` uses a bounded `ThreadPoolExecutor`;
|
|
444
445
|
`serial`/`chained` use a plain sequential loop; `chained` additionally
|
|
@@ -455,13 +456,32 @@ def _dispatch_batch(
|
|
|
455
456
|
- F5: every rejected result's `reason` is a real `ApiError(...).to_json()`
|
|
456
457
|
using an existing `errors.py` taxonomy code.
|
|
457
458
|
|
|
459
|
+
BUG-APIGEN-CLI-002: every batch control-plane field (`operation`,
|
|
460
|
+
`items`, `concurrency`, `mode`, `onItemError`, `itemTimeoutMs`) now nests
|
|
461
|
+
under one top-level `input` object on the wire โ `batch.ts`'s
|
|
462
|
+
`branchInputSchema` matches every other apigen-mounted operation's
|
|
463
|
+
single-JSON-blob convention. `@adhd/apigen-plugin-batch`'s
|
|
464
|
+
`parseBatchRequest` already unwraps this one extra level (its own doc
|
|
465
|
+
comment: "Unwrap that one extra level here โ the only call site that
|
|
466
|
+
reads this shape."); this Python host is a SECOND call site reading the
|
|
467
|
+
same wire shape and must unwrap identically, never operate on the raw
|
|
468
|
+
top-level `body`.
|
|
469
|
+
|
|
458
470
|
Raises:
|
|
459
|
-
ApiError: for whole-request-level rejections (
|
|
460
|
-
unsupported `itemTimeoutMs`, malformed
|
|
461
|
-
`onItemError`/`concurrency`) โ the caller maps
|
|
462
|
-
HTTP 4xx via `_send_error`, the SAME mechanism
|
|
463
|
-
on this server already uses.
|
|
471
|
+
ApiError: for whole-request-level rejections (missing/malformed
|
|
472
|
+
`input`, bad `operation`, unsupported `itemTimeoutMs`, malformed
|
|
473
|
+
`items`/`mode`/`onItemError`/`concurrency`) โ the caller maps
|
|
474
|
+
this to a real HTTP 4xx via `_send_error`, the SAME mechanism
|
|
475
|
+
every other route on this server already uses.
|
|
464
476
|
"""
|
|
477
|
+
input_raw = body.get("input")
|
|
478
|
+
if not isinstance(input_raw, dict):
|
|
479
|
+
raise ApiError(
|
|
480
|
+
"invalid_argument",
|
|
481
|
+
'"input" must be an object containing {operation, items, ...}',
|
|
482
|
+
)
|
|
483
|
+
body = input_raw
|
|
484
|
+
|
|
465
485
|
# F3 โ reject the WHOLE batch upfront; never a silently-weaker
|
|
466
486
|
# wait-only guarantee under the same field name TS uses for real
|
|
467
487
|
# cancellation.
|