Skip to content

Commit ec33e39

Browse files
committed
chore: Updated with recent files and py-executor
1 parent 23b9363 commit ec33e39

File tree

6 files changed

+389
-141
lines changed

6 files changed

+389
-141
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"eslint": "^8.0.0",
3030
"eslint-config-prettier": "^9.0.0",
3131
"prettier": "^3.0.0",
32+
"pyodide": "^0.28.0",
3233
"rimraf": "^6.0.1",
3334
"typescript": "^5.0.0",
3435
"vite": "^7.0.6"

packages/py-executor/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
"clean": "rm -rf dist",
1111
"test": "echo \"No tests specified\""
1212
},
13-
"dependencies": {},
13+
"dependencies": {
14+
"pyodide": "^0.28.0"
15+
},
1416
"devDependencies": {
15-
"typescript": "^5.0.0"
17+
"typescript": "^5.8.3"
1618
},
1719
"files": [
1820
"dist/**/*"

packages/py-executor/pnpm-lock.yaml

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/py-executor/src/index.ts

Lines changed: 134 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,137 @@
1-
// Python executor - Handles Python code execution
1+
// py-executor/src/index.ts
2+
3+
declare const self: DedicatedWorkerGlobalScope;
4+
5+
// Import Pyodide dynamically to avoid module resolution issues
6+
const PYODIDE_VERSION = "0.28.0";
7+
28
export class PythonExecutor {
3-
constructor() {
4-
// Initialize Python executor
5-
}
6-
7-
initialize(): void {
8-
// TODO: Setup postMessage communication with core
9-
// TODO: Setup sandboxed execution environment (likely Pyodide)
10-
}
11-
12-
private executeCode(code: string): void {
13-
// TODO: Implement Python code execution logic
14-
}
9+
private pyodide: any | null = null;
10+
private inputResolve: ((value: string) => void) | null = null;
11+
12+
constructor() {
13+
this.initialize();
14+
}
15+
16+
async initialize(): Promise<void> {
17+
try {
18+
console.log('Starting Pyodide initialization...');
19+
20+
// Dynamically import Pyodide
21+
const pyodideModule = await import(`https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/pyodide.mjs`);
22+
23+
this.pyodide = await pyodideModule.loadPyodide({
24+
indexURL: `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/`
25+
});
26+
27+
console.log('Pyodide loaded successfully');
28+
29+
// Setup output handlers
30+
this.pyodide.setStdout({ batched: (s: string) => self.postMessage({ type: 'stdout', data: s }) });
31+
this.pyodide.setStderr({ batched: (s: string) => self.postMessage({ type: 'stderr', data: s }) });
32+
33+
// Set up async input function for browser communication
34+
this.pyodide.globals.set("browser_input", (prompt: string) => {
35+
return new Promise((resolve) => {
36+
this.inputResolve = resolve;
37+
self.postMessage({ type: 'input_request', prompt: prompt || "Enter value:" });
38+
});
39+
});
40+
41+
console.log('Pyodide setup complete');
42+
self.postMessage({ type: 'ready' });
43+
} catch (error: any) {
44+
console.error('Pyodide initialization error:', error);
45+
self.postMessage({ type: 'error', data: "Failed to load Pyodide: " + error.message });
46+
}
47+
48+
// Setup message handler
49+
self.onmessage = (event: MessageEvent) => this.handleMessage(event);
50+
}
51+
52+
private async handleMessage(event: MessageEvent): Promise<void> {
53+
const { type, value, code } = event.data;
54+
console.log('Worker received message:', { type, value, code: code?.substring(0, 50) + '...' });
55+
56+
if (type === 'input_response' && this.inputResolve) {
57+
this.inputResolve(value || "");
58+
this.inputResolve = null;
59+
return;
60+
}
61+
62+
if (this.pyodide && code) {
63+
this.executeCode(code);
64+
}
65+
}
66+
67+
private transformInputCalls(code: string): string {
68+
// Transform input() calls to await async_input() calls
69+
// This regex handles various input() patterns:
70+
// - input()
71+
// - input("prompt")
72+
// - input('prompt')
73+
// - variable = input(...)
74+
return code.replace(/\binput\s*\(/g, 'await async_input(');
75+
}
76+
77+
private async executeCode(code: string): Promise<void> {
78+
if (!this.pyodide) return;
79+
80+
try {
81+
console.log('Executing Python code...');
82+
await this.pyodide.loadPackagesFromImports(code);
83+
84+
// Setup the async input function in Python
85+
const inputSetup = `
86+
import builtins
87+
88+
# Define async input function that communicates with browser
89+
async def async_input(prompt=""):
90+
"""Async input function that communicates with the browser main thread"""
91+
return await browser_input(str(prompt))
92+
93+
# Keep original input for reference (in case needed)
94+
_original_input = builtins.input
95+
`;
96+
97+
await this.pyodide.runPythonAsync(inputSetup);
98+
99+
// Check if code contains input() calls and transform them
100+
const hasInputCalls = /\binput\s*\(/.test(code);
101+
102+
if (hasInputCalls) {
103+
console.log('Code contains input() calls, transforming to async...');
104+
105+
// Transform input() calls to await async_input()
106+
const transformedCode = this.transformInputCalls(code);
107+
108+
// Wrap in async function to handle await calls
109+
const asyncWrapper = `
110+
import asyncio
111+
112+
async def __main__():
113+
${transformedCode.split('\n').map(line => ' ' + line).join('\n')}
114+
115+
# Run the async main function
116+
await __main__()
117+
`;
118+
console.log('Running transformed async code...');
119+
await this.pyodide.runPythonAsync(asyncWrapper);
120+
} else {
121+
// No input calls, run synchronously
122+
console.log('No input() calls detected, running synchronously...');
123+
await this.pyodide.runPython(code);
124+
}
125+
126+
console.log('Python execution completed');
127+
self.postMessage({ type: 'done' });
128+
} catch (error: any) {
129+
console.error('Python execution error:', error);
130+
self.postMessage({ type: 'error', data: error.message });
131+
}
132+
}
15133
}
16134

17-
export default PythonExecutor;
135+
// Instantiate the executor to start the worker
136+
console.log('Creating PythonExecutor instance...');
137+
new PythonExecutor();

packages/py-executor/tsconfig.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"compilerOptions": {
33
"target": "ES2022",
44
"module": "ESNext",
5-
"moduleResolution": "node",
5+
"moduleResolution": "bundler", // Change from node to bundler
66
"strict": true,
77
"esModuleInterop": true,
88
"skipLibCheck": true,
@@ -12,7 +12,7 @@
1212
"sourceMap": true,
1313
"outDir": "./dist",
1414
"rootDir": "./src",
15-
"lib": ["ES2022", "DOM", "DOM.Iterable"],
15+
"lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"], // Added WebWorker
1616
"allowSyntheticDefaultImports": true,
1717
"resolveJsonModule": true,
1818
"isolatedModules": true,
@@ -22,4 +22,4 @@
2222
},
2323
"include": ["src/**/*"],
2424
"exclude": ["dist", "node_modules"]
25-
}
25+
}

0 commit comments

Comments
 (0)