Update app.py
Browse files
app.py
CHANGED
|
@@ -60,7 +60,6 @@ class WebSocketInputOutput:
|
|
| 60 |
"""Reads a line from stdin, waiting for input from WebSocket."""
|
| 61 |
try:
|
| 62 |
print("DEBUG: Sending input_request to client.")
|
| 63 |
-
# CORRECTED: Access send_text via self.websocket
|
| 64 |
await self.websocket.send_text(json.dumps({"type": "input_request"}))
|
| 65 |
line = await self.input_queue.get() # Wait for input from client
|
| 66 |
print(f"DEBUG: Received input from queue: {line.strip()[:50]}...")
|
|
@@ -79,7 +78,6 @@ class WebSocketInputOutput:
|
|
| 79 |
"""Synchronous wrapper for readline for use with exec."""
|
| 80 |
try:
|
| 81 |
print("DEBUG: Synchronous input wrapper called.")
|
| 82 |
-
# This is called from the executor thread, so asyncio.run is appropriate here
|
| 83 |
return asyncio.run(self.readline())
|
| 84 |
except Exception as e:
|
| 85 |
print(f"ERROR: Synchronous input wrapper error: {e}")
|
|
@@ -386,30 +384,44 @@ class SecurePythonExecutor:
|
|
| 386 |
print(f"DEBUG: run_with_timeout called. Client: {websocket_io.websocket.client}")
|
| 387 |
|
| 388 |
# Define the synchronous target function to be run in the executor thread
|
| 389 |
-
def
|
| 390 |
-
# This function runs in a separate thread managed by the event loop's executor
|
| 391 |
nonlocal result
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
|
| 403 |
# Use the event loop's default ThreadPoolExecutor to run the synchronous code
|
| 404 |
loop = asyncio.get_running_loop()
|
| 405 |
try:
|
| 406 |
-
print(f"DEBUG: Scheduling
|
| 407 |
-
# Run the synchronous
|
| 408 |
await asyncio.wait_for(
|
| 409 |
-
loop.run_in_executor(None,
|
| 410 |
timeout=self.timeout
|
| 411 |
)
|
| 412 |
-
print("DEBUG:
|
| 413 |
except asyncio.TimeoutError:
|
| 414 |
print(f"ERROR: Code execution timed out after {self.timeout} seconds in run_in_executor.")
|
| 415 |
result['error'] = f"Code execution timed out after {self.timeout} seconds"
|
|
@@ -419,8 +431,7 @@ class SecurePythonExecutor:
|
|
| 419 |
except Exception:
|
| 420 |
pass # Ignore if websocket is already closed
|
| 421 |
except Exception as e:
|
| 422 |
-
# This catches exceptions from
|
| 423 |
-
# (e.g., if the thread itself crashes in an unrecoverable way before our try/except)
|
| 424 |
print(f"ERROR: Unexpected exception from executor: {e}")
|
| 425 |
result['error'] = f"Unexpected executor error: {type(e).__name__}: {str(e)}"
|
| 426 |
result['success'] = False
|
|
@@ -430,21 +441,20 @@ class SecurePythonExecutor:
|
|
| 430 |
pass
|
| 431 |
|
| 432 |
# After execution (or timeout), collect output
|
| 433 |
-
if result['success']:
|
| 434 |
result['output'] = websocket_io.output_buffer.getvalue()
|
| 435 |
-
if websocket_io.error_buffer.getvalue():
|
| 436 |
result['error'] = result['error'] + "\n" + websocket_io.error_buffer.getvalue() if result['error'] else websocket_io.error_buffer.getvalue()
|
| 437 |
-
result['success'] = False
|
| 438 |
|
| 439 |
# Send a final message to the client indicating execution is complete
|
| 440 |
try:
|
| 441 |
print(f"DEBUG: Attempting to send execution_complete message (success={result['success']}).")
|
| 442 |
-
# CORRECTED: Access send_text via websocket_io.websocket
|
| 443 |
await websocket_io.websocket.send_text(json.dumps({"type": "execution_complete", "result": result}))
|
| 444 |
print("DEBUG: execution_complete message sent.")
|
| 445 |
except WebSocketDisconnect:
|
| 446 |
print("DEBUG: WebSocketDisconnect when sending execution_complete.")
|
| 447 |
-
pass
|
| 448 |
except Exception as e:
|
| 449 |
print(f"ERROR: Error sending execution_complete message: {e}")
|
| 450 |
|
|
@@ -466,8 +476,6 @@ app = FastAPI(
|
|
| 466 |
@app.get("/", response_class=HTMLResponse)
|
| 467 |
async def get_index():
|
| 468 |
"""Serves the interactive frontend."""
|
| 469 |
-
# This assumes index.html is in the same directory as app.py
|
| 470 |
-
# Explicitly copy index.html to /app/index.html to avoid any ambiguity with '.'
|
| 471 |
with open("index.html", "r") as f:
|
| 472 |
return HTMLResponse(content=f.read())
|
| 473 |
|
|
@@ -487,15 +495,12 @@ async def websocket_endpoint(websocket: WebSocket):
|
|
| 487 |
if data["type"] == "code":
|
| 488 |
code = data["content"]
|
| 489 |
print(f"Received code from {websocket_io.websocket.client}: {code[:50]}...")
|
| 490 |
-
# Execute code asynchronously in the background
|
| 491 |
asyncio.create_task(executor.execute_code(code, websocket_io))
|
| 492 |
elif data["type"] == "input":
|
| 493 |
user_input = data["content"]
|
| 494 |
print(f"Received input from {websocket_io.websocket.client}: {user_input.strip()[:50]}...")
|
| 495 |
-
await websocket_io.input_queue.put(user_input + "\n")
|
| 496 |
elif data["type"] == "ping":
|
| 497 |
-
# Respond to pings to keep connection alive
|
| 498 |
-
# CORRECTED: Access send_text via websocket_io.websocket
|
| 499 |
await websocket_io.websocket.send_text(json.dumps({"type": "pong"}))
|
| 500 |
print(f"DEBUG: Sent pong to {websocket_io.websocket.client}")
|
| 501 |
else:
|
|
@@ -509,7 +514,6 @@ async def websocket_endpoint(websocket: WebSocket):
|
|
| 509 |
print(f"WebSocket error for {websocket.client}: {e}")
|
| 510 |
finally:
|
| 511 |
websocket_io.closed = True
|
| 512 |
-
# Clean up any pending input requests if the client disconnects
|
| 513 |
while not websocket_io.input_queue.empty():
|
| 514 |
try:
|
| 515 |
websocket_io.input_queue.get_nowait()
|
|
@@ -517,8 +521,6 @@ async def websocket_endpoint(websocket: WebSocket):
|
|
| 517 |
pass
|
| 518 |
print(f"WebSocket connection closed for {websocket_io.websocket.client}")
|
| 519 |
|
| 520 |
-
# This block is for local development using `uvicorn`
|
| 521 |
-
# Hugging Face Spaces will use `uvicorn` directly via the Dockerfile CMD
|
| 522 |
if __name__ == "__main__":
|
| 523 |
import uvicorn
|
| 524 |
print("Starting Interactive Python Code Executor API (FastAPI)...")
|
|
@@ -532,10 +534,10 @@ if __name__ == "__main__":
|
|
| 532 |
|
| 533 |
try:
|
| 534 |
uvicorn.run(
|
| 535 |
-
"app:app",
|
| 536 |
host=server_name,
|
| 537 |
port=server_port,
|
| 538 |
-
reload=False,
|
| 539 |
log_level="info"
|
| 540 |
)
|
| 541 |
except Exception as e:
|
|
|
|
| 60 |
"""Reads a line from stdin, waiting for input from WebSocket."""
|
| 61 |
try:
|
| 62 |
print("DEBUG: Sending input_request to client.")
|
|
|
|
| 63 |
await self.websocket.send_text(json.dumps({"type": "input_request"}))
|
| 64 |
line = await self.input_queue.get() # Wait for input from client
|
| 65 |
print(f"DEBUG: Received input from queue: {line.strip()[:50]}...")
|
|
|
|
| 78 |
"""Synchronous wrapper for readline for use with exec."""
|
| 79 |
try:
|
| 80 |
print("DEBUG: Synchronous input wrapper called.")
|
|
|
|
| 81 |
return asyncio.run(self.readline())
|
| 82 |
except Exception as e:
|
| 83 |
print(f"ERROR: Synchronous input wrapper error: {e}")
|
|
|
|
| 384 |
print(f"DEBUG: run_with_timeout called. Client: {websocket_io.websocket.client}")
|
| 385 |
|
| 386 |
# Define the synchronous target function to be run in the executor thread
|
| 387 |
+
def _execute_in_thread_test2(): # Renamed to test2 for clarity
|
|
|
|
| 388 |
nonlocal result
|
| 389 |
+
original_stdin = sys.stdin
|
| 390 |
+
original_stdout = sys.stdout
|
| 391 |
+
original_stderr = sys.stderr
|
| 392 |
+
try:
|
| 393 |
+
sys.stdin = websocket_io
|
| 394 |
+
sys.stdout = websocket_io
|
| 395 |
+
sys.stderr = websocket_io
|
| 396 |
+
print(f"DEBUG: Executor thread '{threading.current_thread().name}' started. IO redirected.")
|
| 397 |
+
print("DEBUG: This should now go via websocket_io.write!")
|
| 398 |
+
|
| 399 |
+
# We are still not calling exec here, just testing sys.stdout print
|
| 400 |
+
print("Hello from redirected stdout!")
|
| 401 |
+
|
| 402 |
+
result['success'] = True
|
| 403 |
+
except Exception as e:
|
| 404 |
+
error_msg = f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()}"
|
| 405 |
+
print(f"ERROR: Exception in _execute_in_thread_test2 during sys redirection test: {error_msg}")
|
| 406 |
+
websocket_io.error_buffer.write(error_msg)
|
| 407 |
+
result['success'] = False
|
| 408 |
+
finally:
|
| 409 |
+
sys.stdin = original_stdin
|
| 410 |
+
sys.stdout = original_stdout
|
| 411 |
+
sys.stderr = original_stderr
|
| 412 |
+
print(f"DEBUG: Executor thread '{threading.current_thread().name}' finished. IO restored.")
|
| 413 |
+
|
| 414 |
|
| 415 |
# Use the event loop's default ThreadPoolExecutor to run the synchronous code
|
| 416 |
loop = asyncio.get_running_loop()
|
| 417 |
try:
|
| 418 |
+
print(f"DEBUG: Scheduling _execute_in_thread_test2 in executor with timeout {self.timeout}s.")
|
| 419 |
+
# Run the synchronous _execute_in_thread_test2 in a separate thread
|
| 420 |
await asyncio.wait_for(
|
| 421 |
+
loop.run_in_executor(None, _execute_in_thread_test2), # Changed to _execute_in_thread_test2
|
| 422 |
timeout=self.timeout
|
| 423 |
)
|
| 424 |
+
print("DEBUG: _execute_in_thread_test2 completed via executor.")
|
| 425 |
except asyncio.TimeoutError:
|
| 426 |
print(f"ERROR: Code execution timed out after {self.timeout} seconds in run_in_executor.")
|
| 427 |
result['error'] = f"Code execution timed out after {self.timeout} seconds"
|
|
|
|
| 431 |
except Exception:
|
| 432 |
pass # Ignore if websocket is already closed
|
| 433 |
except Exception as e:
|
| 434 |
+
# This catches exceptions from _execute_in_thread_test2 that propagate up
|
|
|
|
| 435 |
print(f"ERROR: Unexpected exception from executor: {e}")
|
| 436 |
result['error'] = f"Unexpected executor error: {type(e).__name__}: {str(e)}"
|
| 437 |
result['success'] = False
|
|
|
|
| 441 |
pass
|
| 442 |
|
| 443 |
# After execution (or timeout), collect output
|
| 444 |
+
if result['success']:
|
| 445 |
result['output'] = websocket_io.output_buffer.getvalue()
|
| 446 |
+
if websocket_io.error_buffer.getvalue():
|
| 447 |
result['error'] = result['error'] + "\n" + websocket_io.error_buffer.getvalue() if result['error'] else websocket_io.error_buffer.getvalue()
|
| 448 |
+
result['success'] = False
|
| 449 |
|
| 450 |
# Send a final message to the client indicating execution is complete
|
| 451 |
try:
|
| 452 |
print(f"DEBUG: Attempting to send execution_complete message (success={result['success']}).")
|
|
|
|
| 453 |
await websocket_io.websocket.send_text(json.dumps({"type": "execution_complete", "result": result}))
|
| 454 |
print("DEBUG: execution_complete message sent.")
|
| 455 |
except WebSocketDisconnect:
|
| 456 |
print("DEBUG: WebSocketDisconnect when sending execution_complete.")
|
| 457 |
+
pass
|
| 458 |
except Exception as e:
|
| 459 |
print(f"ERROR: Error sending execution_complete message: {e}")
|
| 460 |
|
|
|
|
| 476 |
@app.get("/", response_class=HTMLResponse)
|
| 477 |
async def get_index():
|
| 478 |
"""Serves the interactive frontend."""
|
|
|
|
|
|
|
| 479 |
with open("index.html", "r") as f:
|
| 480 |
return HTMLResponse(content=f.read())
|
| 481 |
|
|
|
|
| 495 |
if data["type"] == "code":
|
| 496 |
code = data["content"]
|
| 497 |
print(f"Received code from {websocket_io.websocket.client}: {code[:50]}...")
|
|
|
|
| 498 |
asyncio.create_task(executor.execute_code(code, websocket_io))
|
| 499 |
elif data["type"] == "input":
|
| 500 |
user_input = data["content"]
|
| 501 |
print(f"Received input from {websocket_io.websocket.client}: {user_input.strip()[:50]}...")
|
| 502 |
+
await websocket_io.input_queue.put(user_input + "\n")
|
| 503 |
elif data["type"] == "ping":
|
|
|
|
|
|
|
| 504 |
await websocket_io.websocket.send_text(json.dumps({"type": "pong"}))
|
| 505 |
print(f"DEBUG: Sent pong to {websocket_io.websocket.client}")
|
| 506 |
else:
|
|
|
|
| 514 |
print(f"WebSocket error for {websocket.client}: {e}")
|
| 515 |
finally:
|
| 516 |
websocket_io.closed = True
|
|
|
|
| 517 |
while not websocket_io.input_queue.empty():
|
| 518 |
try:
|
| 519 |
websocket_io.input_queue.get_nowait()
|
|
|
|
| 521 |
pass
|
| 522 |
print(f"WebSocket connection closed for {websocket_io.websocket.client}")
|
| 523 |
|
|
|
|
|
|
|
| 524 |
if __name__ == "__main__":
|
| 525 |
import uvicorn
|
| 526 |
print("Starting Interactive Python Code Executor API (FastAPI)...")
|
|
|
|
| 534 |
|
| 535 |
try:
|
| 536 |
uvicorn.run(
|
| 537 |
+
"app:app",
|
| 538 |
host=server_name,
|
| 539 |
port=server_port,
|
| 540 |
+
reload=False,
|
| 541 |
log_level="info"
|
| 542 |
)
|
| 543 |
except Exception as e:
|