chmielvu commited on
Commit
382640f
Β·
verified Β·
1 Parent(s): 4ad9215

Initial: FastAPI code executor with sessions

Browse files
Files changed (3) hide show
  1. Dockerfile +18 -0
  2. README.md +10 -3
  3. server.py +214 -0
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ RUN apt-get update && apt-get install -y --no-install-recommends \
4
+ curl \
5
+ && rm -rf /var/lib/apt/lists/*
6
+
7
+ RUN pip install --no-cache-dir \
8
+ fastapi uvicorn pydantic \
9
+ numpy pandas scipy networkx scikit-learn \
10
+ torch --index-url https://download.pytorch.org/whl/cpu \
11
+ transformers \
12
+ matplotlib pillow requests beautifulsoup4
13
+
14
+ WORKDIR /app
15
+ COPY server.py .
16
+
17
+ ENV PORT=7860
18
+ CMD ["python", "server.py"]
README.md CHANGED
@@ -1,10 +1,17 @@
1
  ---
2
  title: Code Executor Pro
3
- emoji: πŸš€
4
  colorFrom: blue
5
- colorTo: blue
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
1
  ---
2
  title: Code Executor Pro
3
+ emoji: πŸ”§
4
  colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
+ short_description: Multi-language code executor with sessions
9
  ---
10
 
11
+ # Code Executor Pro
12
+
13
+ Mashup of `kgot-python-executor` (simple API), `librechat-code-interpreter` (JSON output), and `code-interpreter-pro` (session persistence).
14
+
15
+ **API:** `POST /run {"code":"...", "language":"python|javascript|bash", "session_id":"optional"}`
16
+
17
+ **Pre-installed:** numpy, pandas, scipy, networkx, scikit-learn, torch (CPU), transformers, matplotlib
server.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Code Executor Pro β€” ultimate mashup server
3
+ FastAPI, session persistence, JSON output, multi-language, file workspace
4
+ """
5
+ from fastapi import FastAPI, HTTPException, UploadFile, File, Form
6
+ from fastapi.responses import JSONResponse
7
+ from pydantic import BaseModel, Field
8
+ import uvicorn
9
+ import sys
10
+ import io
11
+ import traceback
12
+ import subprocess
13
+ import os
14
+ import json
15
+ import tempfile
16
+ import time
17
+ import uuid
18
+ from pathlib import Path
19
+
20
+ app = FastAPI(title="Code Executor Pro", version="1.0.0")
21
+
22
+ WORKSPACE_ROOT = Path("/workspace")
23
+ WORKSPACE_ROOT.mkdir(exist_ok=True)
24
+
25
+ # ── Session Manager ──────────────────────────────────────────────────────────
26
+ class Session:
27
+ def __init__(self):
28
+ self.vars = {}
29
+ self.created_at = time.time()
30
+ self.last_access = time.time()
31
+
32
+ sessions: dict[str, Session] = {}
33
+ SESSION_TIMEOUT = 3600 # 1 hour
34
+
35
+ def get_or_create_session(session_id: str | None) -> tuple[str, Session]:
36
+ if not session_id:
37
+ session_id = uuid.uuid4().hex[:12]
38
+ if session_id not in sessions:
39
+ sessions[session_id] = Session()
40
+ sess = sessions[session_id]
41
+ sess.last_access = time.time()
42
+ # Clean stale sessions
43
+ now = time.time()
44
+ stale = [k for k, v in sessions.items() if now - v.last_access > SESSION_TIMEOUT]
45
+ for k in stale:
46
+ del sessions[k]
47
+ return session_id, sess
48
+
49
+ # ── Request Models ───────────────────────────────────────────────────────────
50
+ class RunRequest(BaseModel):
51
+ code: str = Field(..., description="Code to execute")
52
+ language: str = Field("python", description="python | javascript | bash")
53
+ session_id: str | None = Field(None, description="Session ID for state persistence")
54
+ timeout: int = Field(30, description="Max execution time in seconds")
55
+
56
+ class RunResponse(BaseModel):
57
+ success: bool
58
+ stdout: str
59
+ stderr: str
60
+ error: str | None = None
61
+ execution_time_ms: int
62
+ session_id: str
63
+ files_created: list[str] = []
64
+
65
+ # ── Executors ────────────────────────────────────────────────────────────────
66
+ def execute_python(code: str, session_vars: dict, workspace: Path, timeout: int):
67
+ stdout_capture = io.StringIO()
68
+ stderr_capture = io.StringIO()
69
+ old_stdout, old_stderr = sys.stdout, sys.stderr
70
+
71
+ try:
72
+ sys.stdout = stdout_capture
73
+ sys.stderr = stderr_capture
74
+
75
+ exec_globals = {
76
+ '__builtins__': __builtins__,
77
+ 'WORKSPACE': str(workspace),
78
+ }
79
+ exec_globals.update(session_vars)
80
+
81
+ exec(code, exec_globals)
82
+
83
+ session_vars.clear()
84
+ session_vars.update({
85
+ k: v for k, v in exec_globals.items()
86
+ if not k.startswith('__') and k not in ['WORKSPACE']
87
+ })
88
+
89
+ return stdout_capture.getvalue(), stderr_capture.getvalue(), None
90
+ except Exception as e:
91
+ return stdout_capture.getvalue(), stderr_capture.getvalue(), f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()}"
92
+ finally:
93
+ sys.stdout, sys.stderr = old_stdout, old_stderr
94
+
95
+ def execute_javascript(code: str, timeout: int):
96
+ try:
97
+ result = subprocess.run(
98
+ ["node", "-e", code],
99
+ capture_output=True, text=True, timeout=timeout
100
+ )
101
+ return result.stdout, result.stderr, None if result.returncode == 0 else f"exit code {result.returncode}"
102
+ except subprocess.TimeoutExpired:
103
+ return "", "", f"Timeout after {timeout}s"
104
+ except FileNotFoundError:
105
+ return "", "", "Node.js not installed"
106
+ except Exception as e:
107
+ return "", "", str(e)
108
+
109
+ def execute_bash(code: str, timeout: int):
110
+ try:
111
+ result = subprocess.run(
112
+ ["bash", "-c", code],
113
+ capture_output=True, text=True, timeout=timeout
114
+ )
115
+ return result.stdout, result.stderr, None if result.returncode == 0 else f"exit code {result.returncode}"
116
+ except subprocess.TimeoutExpired:
117
+ return "", "", f"Timeout after {timeout}s"
118
+ except Exception as e:
119
+ return "", "", str(e)
120
+
121
+ # ── API Routes ───────────────────────────────────────────────────────────────
122
+ @app.get("/health")
123
+ def health():
124
+ return {"status": "ok", "sessions_active": len(sessions)}
125
+
126
+ @app.post("/run", response_model=RunResponse)
127
+ def run_code(req: RunRequest):
128
+ session_id, session = get_or_create_session(req.session_id)
129
+ workspace = WORKSPACE_ROOT / session_id
130
+ workspace.mkdir(exist_ok=True)
131
+
132
+ start = time.time()
133
+ stdout, stderr, error = "", "", None
134
+
135
+ try:
136
+ if req.language == "python":
137
+ stdout, stderr, error = execute_python(req.code, session.vars, workspace, req.timeout)
138
+ elif req.language == "javascript":
139
+ stdout, stderr, error = execute_javascript(req.code, req.timeout)
140
+ elif req.language == "bash":
141
+ stdout, stderr, error = execute_bash(req.code, req.timeout)
142
+ else:
143
+ raise HTTPException(400, f"Unsupported language: {req.language}")
144
+ except Exception as e:
145
+ error = f"{type(e).__name__}: {str(e)}"
146
+ stdout = ""
147
+ stderr = ""
148
+
149
+ elapsed_ms = int((time.time() - start) * 1000)
150
+
151
+ # List files created in workspace
152
+ files = [str(f.name) for f in workspace.iterdir() if f.is_file()]
153
+
154
+ return RunResponse(
155
+ success=error is None,
156
+ stdout=stdout,
157
+ stderr=stderr,
158
+ error=error,
159
+ execution_time_ms=elapsed_ms,
160
+ session_id=session_id,
161
+ files_created=files,
162
+ )
163
+
164
+ @app.post("/session/reset")
165
+ def reset_session(session_id: str = Form(...)):
166
+ if session_id in sessions:
167
+ del sessions[session_id]
168
+ return {"status": "reset", "session_id": session_id}
169
+
170
+ @app.get("/session/{session_id}/vars")
171
+ def list_session_vars(session_id: str):
172
+ if session_id not in sessions:
173
+ raise HTTPException(404, "Session not found")
174
+ return {"session_id": session_id, "vars": list(sessions[session_id].vars.keys())}
175
+
176
+ @app.post("/files/upload/{session_id}")
177
+ async def upload_file(session_id: str, file: UploadFile = File(...)):
178
+ workspace = WORKSPACE_ROOT / session_id
179
+ workspace.mkdir(exist_ok=True)
180
+ dest = workspace / file.filename
181
+ content = await file.read()
182
+ dest.write_bytes(content)
183
+ return {"status": "ok", "file": file.filename, "size": len(content)}
184
+
185
+ @app.get("/files/{session_id}")
186
+ def list_files(session_id: str):
187
+ workspace = WORKSPACE_ROOT / session_id
188
+ if not workspace.exists():
189
+ return {"session_id": session_id, "files": []}
190
+ files = []
191
+ for f in workspace.iterdir():
192
+ if f.is_file():
193
+ files.append({"name": f.name, "size": f.stat().st_size})
194
+ return {"session_id": session_id, "files": files}
195
+
196
+ @app.get("/")
197
+ def root():
198
+ return {
199
+ "service": "Code Executor Pro",
200
+ "version": "1.0.0",
201
+ "languages": ["python", "javascript", "bash"],
202
+ "endpoints": {
203
+ "POST /run": "Execute code",
204
+ "POST /session/reset": "Reset session",
205
+ "GET /session/{id}/vars": "List session variables",
206
+ "POST /files/upload/{id}": "Upload file",
207
+ "GET /files/{id}": "List workspace files",
208
+ "GET /health": "Health check",
209
+ }
210
+ }
211
+
212
+ if __name__ == "__main__":
213
+ port = int(os.environ.get("PORT", 7860))
214
+ uvicorn.run(app, host="0.0.0.0", port=port)