1+ """REST API server for CodeRunner - InstaVM compatible interface"""
2+
3+ from fastapi import FastAPI , HTTPException , Depends
4+ from fastapi .middleware .cors import CORSMiddleware
5+ from fastapi .responses import JSONResponse
6+ import logging
7+ import time
8+ import uuid
9+ from typing import Dict , Any
10+
11+ from .schemas .execution import (
12+ CommandRequest , ExecutionResponse , AsyncExecutionResponse ,
13+ SessionResponse , HealthResponse
14+ )
15+ from ..core .session_manager import session_manager , SessionError
16+ from ..core .language_processor import LanguageProcessor
17+ from ..core .exceptions import ExecutionError , KernelError
18+
19+ logger = logging .getLogger (__name__ )
20+
21+ # Create FastAPI app
22+ app = FastAPI (
23+ title = "CodeRunner REST API" ,
24+ description = "Local code execution with InstaVM-compatible interface" ,
25+ version = "1.0.0" ,
26+ docs_url = "/docs" ,
27+ redoc_url = "/redoc"
28+ )
29+
30+ # Add CORS middleware
31+ app .add_middleware (
32+ CORSMiddleware ,
33+ allow_origins = ["*" ],
34+ allow_credentials = True ,
35+ allow_methods = ["*" ],
36+ allow_headers = ["*" ],
37+ )
38+
39+ # Async task storage (simple in-memory for now)
40+ async_tasks : Dict [str , Dict [str , Any ]] = {}
41+
42+
43+ @app .on_event ("startup" )
44+ async def startup_event ():
45+ """Initialize services on startup"""
46+ try :
47+ await session_manager .initialize ()
48+ logger .info ("REST API server started successfully" )
49+ except Exception as e :
50+ logger .error (f"Failed to start REST API server: { e } " )
51+ raise
52+
53+
54+ @app .on_event ("shutdown" )
55+ async def shutdown_event ():
56+ """Cleanup on shutdown"""
57+ try :
58+ await session_manager .shutdown ()
59+ logger .info ("REST API server shutdown complete" )
60+ except Exception as e :
61+ logger .error (f"Error during shutdown: { e } " )
62+
63+
64+ @app .get ("/health" , response_model = HealthResponse )
65+ async def health_check ():
66+ """Health check endpoint"""
67+ try :
68+ # Check session manager
69+ session_count = len (session_manager .sessions )
70+
71+ # Check kernel pool
72+ from server import kernel_pool
73+ kernel_status = {
74+ "total_kernels" : len (kernel_pool .kernels ),
75+ "available_kernels" : len ([k for k in kernel_pool .kernels .values () if k .is_available ()]),
76+ "busy_kernels" : len (kernel_pool .busy_kernels )
77+ }
78+
79+ return HealthResponse (
80+ status = "healthy" ,
81+ version = "1.0.0" ,
82+ services = {
83+ "session_manager" : {"active_sessions" : session_count },
84+ "kernel_pool" : kernel_status
85+ }
86+ )
87+ except Exception as e :
88+ logger .error (f"Health check failed: { e } " )
89+ raise HTTPException (status_code = 503 , detail = f"Service unhealthy: { str (e )} " )
90+
91+
92+ @app .post ("/execute" , response_model = ExecutionResponse )
93+ async def execute_command (request : CommandRequest ):
94+ """
95+ Execute command synchronously - InstaVM compatible interface
96+
97+ Args:
98+ request: Command execution request
99+
100+ Returns:
101+ Execution results with stdout, stderr, timing
102+ """
103+ start_time = time .time ()
104+ cpu_start_time = time .process_time ()
105+
106+ try :
107+ # Validate language
108+ if not LanguageProcessor .validate_language (request .language ):
109+ raise HTTPException (
110+ status_code = 400 ,
111+ detail = f"Unsupported language: { request .language } "
112+ )
113+
114+ # Get or create session
115+ session_id = request .session_id
116+ if not session_id :
117+ session_id = await session_manager .create_session (request .language )
118+ elif not await session_manager .get_session (session_id ):
119+ raise HTTPException (status_code = 404 , detail = "Session not found" )
120+
121+ # Execute command
122+ try :
123+ result = await session_manager .execute_in_session (session_id , request .command )
124+ except SessionError as e :
125+ raise HTTPException (status_code = 400 , detail = str (e ))
126+ except Exception as e :
127+ raise HTTPException (status_code = 500 , detail = f"Execution failed: { str (e )} " )
128+
129+ # Calculate timing
130+ execution_time = time .time () - start_time
131+ cpu_time = time .process_time () - cpu_start_time
132+
133+ # Extract output from result
134+ stdout = ""
135+ stderr = ""
136+
137+ if isinstance (result , dict ):
138+ # Handle different result formats from kernel pool
139+ if "stdout" in result :
140+ stdout = result ["stdout" ]
141+ elif "output" in result :
142+ stdout = result ["output" ]
143+ else :
144+ stdout = str (result )
145+
146+ if "stderr" in result :
147+ stderr = result ["stderr" ]
148+ elif "error" in result and result .get ("error" ):
149+ stderr = str (result ["error" ])
150+ else :
151+ stdout = str (result )
152+
153+ return ExecutionResponse (
154+ stdout = stdout ,
155+ stderr = stderr ,
156+ execution_time = execution_time ,
157+ cpu_time = cpu_time ,
158+ session_id = session_id
159+ )
160+
161+ except HTTPException :
162+ raise
163+ except Exception as e :
164+ logger .error (f"Unexpected error in execute_command: { e } " )
165+ raise HTTPException (status_code = 500 , detail = f"Internal server error: { str (e )} " )
166+
167+
168+ @app .post ("/execute_async" , response_model = AsyncExecutionResponse )
169+ async def execute_command_async (request : CommandRequest ):
170+ """
171+ Execute command asynchronously - InstaVM compatible interface
172+
173+ Args:
174+ request: Command execution request
175+
176+ Returns:
177+ Task ID for checking execution status
178+ """
179+ try :
180+ # Validate language
181+ if not LanguageProcessor .validate_language (request .language ):
182+ raise HTTPException (
183+ status_code = 400 ,
184+ detail = f"Unsupported language: { request .language } "
185+ )
186+
187+ # Generate task ID
188+ task_id = str (uuid .uuid4 ())
189+
190+ # Store task info
191+ async_tasks [task_id ] = {
192+ "id" : task_id ,
193+ "status" : "queued" ,
194+ "command" : request .command ,
195+ "language" : request .language ,
196+ "session_id" : request .session_id ,
197+ "created_at" : time .time (),
198+ "result" : None ,
199+ "error" : None
200+ }
201+
202+ # TODO: Implement actual async execution with background tasks
203+ # For now, just return the task ID
204+ # In a full implementation, this would use Celery or similar
205+
206+ return AsyncExecutionResponse (task_id = task_id , status = "queued" )
207+
208+ except HTTPException :
209+ raise
210+ except Exception as e :
211+ logger .error (f"Error in execute_command_async: { e } " )
212+ raise HTTPException (status_code = 500 , detail = f"Internal server error: { str (e )} " )
213+
214+
215+ @app .get ("/tasks/{task_id}" )
216+ async def get_task_status (task_id : str ):
217+ """Get async task status - InstaVM compatible"""
218+ task = async_tasks .get (task_id )
219+ if not task :
220+ raise HTTPException (status_code = 404 , detail = "Task not found" )
221+
222+ return task
223+
224+
225+ @app .post ("/sessions" , response_model = SessionResponse )
226+ async def create_session (language : str = "python" ):
227+ """
228+ Create new execution session
229+
230+ Args:
231+ language: Programming language for session
232+
233+ Returns:
234+ Session information
235+ """
236+ try :
237+ session_id = await session_manager .create_session (language )
238+
239+ return SessionResponse (
240+ session_id = session_id ,
241+ status = "active" ,
242+ created_at = time .time ()
243+ )
244+
245+ except SessionError as e :
246+ raise HTTPException (status_code = 400 , detail = str (e ))
247+ except Exception as e :
248+ logger .error (f"Error creating session: { e } " )
249+ raise HTTPException (status_code = 500 , detail = f"Failed to create session: { str (e )} " )
250+
251+
252+ @app .get ("/sessions/{session_id}" )
253+ async def get_session (session_id : str ):
254+ """Get session information"""
255+ session = await session_manager .get_session (session_id )
256+ if not session :
257+ raise HTTPException (status_code = 404 , detail = "Session not found" )
258+
259+ return {
260+ "session_id" : session .id ,
261+ "language" : session .language ,
262+ "created_at" : session .created_at .isoformat (),
263+ "last_used" : session .last_used .isoformat (),
264+ "status" : "active"
265+ }
266+
267+
268+ @app .delete ("/sessions/{session_id}" )
269+ async def close_session (session_id : str ):
270+ """Close execution session"""
271+ success = await session_manager .close_session (session_id )
272+ if not success :
273+ raise HTTPException (status_code = 404 , detail = "Session not found" )
274+
275+ return {"success" : True , "message" : f"Session { session_id } closed" }
276+
277+
278+ @app .get ("/sessions" )
279+ async def list_sessions ():
280+ """List all active sessions"""
281+ sessions = await session_manager .list_sessions ()
282+ return {"sessions" : sessions }
283+
284+
285+ @app .get ("/languages" )
286+ async def get_supported_languages ():
287+ """Get list of supported programming languages"""
288+ return {
289+ "languages" : LanguageProcessor .get_supported_languages (),
290+ "default" : "python"
291+ }
292+
293+
294+ # Error handlers
295+ @app .exception_handler (SessionError )
296+ async def session_error_handler (request , exc ):
297+ return JSONResponse (
298+ status_code = 400 ,
299+ content = {"detail" : str (exc ), "type" : "session_error" }
300+ )
301+
302+
303+ @app .exception_handler (ExecutionError )
304+ async def execution_error_handler (request , exc ):
305+ return JSONResponse (
306+ status_code = 500 ,
307+ content = {"detail" : str (exc ), "type" : "execution_error" }
308+ )
309+
310+
311+ @app .exception_handler (KernelError )
312+ async def kernel_error_handler (request , exc ):
313+ return JSONResponse (
314+ status_code = 503 ,
315+ content = {"detail" : str (exc ), "type" : "kernel_error" }
316+ )
317+
318+
319+ if __name__ == "__main__" :
320+ import uvicorn
321+ uvicorn .run (app , host = "0.0.0.0" , port = 8223 )
0 commit comments