python-enviroment / server.py
Karan6933's picture
Upload 7 files
442ee00 verified
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
import json
import asyncio
import os
import uuid
import shutil
from runner import ProcessRunner
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
PROJECTS_DIR = os.path.join(os.path.dirname(__file__), "projects")
if not os.path.exists(PROJECTS_DIR):
os.makedirs(PROJECTS_DIR)
active_runners = {}
def cleanup_old_projects():
try:
projects = []
for name in os.listdir(PROJECTS_DIR):
path = os.path.join(PROJECTS_DIR, name)
if os.path.isdir(path):
mtime = os.stat(path).st_mtime
projects.append((mtime, name, path))
# Sort by modification time, newest first
projects.sort(reverse=True)
# Keep the 4 newest, delete the rest so we have max 5 after creating a new one
for _, name, path in projects[4:]:
if name not in active_runners:
shutil.rmtree(path, ignore_errors=True)
except Exception as e:
print(f"Error cleaning up projects: {e}")
@app.websocket("/ws/run")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
cleanup_old_projects()
project_id = str(uuid.uuid4())
runner = ProcessRunner(project_id, PROJECTS_DIR)
active_runners[project_id] = runner
try:
while True:
data = await websocket.receive_text()
payload = json.loads(data)
files = payload.get("files", [])
if not files:
await websocket.send_text(json.dumps({"type": "error", "message": "No files provided"}))
continue
async def log_callback(msg: str):
await websocket.send_text(json.dumps({"type": "log", "content": msg}))
await log_callback(f"✦ Preparing environment for project {project_id}...\n")
await runner.setup(files)
await log_callback("✦ Synchronizing dependencies...\n")
await runner.install_dependencies(log_callback)
await log_callback("🚀 Launching application...\n")
await runner.run(log_callback)
# Keep connection open and monitor for timeout or closure
# In a real production app, we'd have more complex process management here
try:
# Wait for disconnect or some signal from client
while True:
await asyncio.wait_for(websocket.receive_text(), timeout=3600)
except asyncio.TimeoutError:
await log_callback("\n[System] Session heartbeat timeout (1 hour).\n")
except WebSocketDisconnect:
print(f"Client disconnected: {project_id}")
except Exception as e:
print(f"Error: {e}")
try:
await websocket.send_text(json.dumps({"type": "error", "message": str(e)}))
except:
pass
finally:
await runner.stop()
# runner.cleanup() # Optional: keep files for debugging or clean them up
if project_id in active_runners:
del active_runners[project_id]
if __name__ == "__main__":
import uvicorn
# Hugging Face Spaces expose port 7860 by default
uvicorn.run(app, host="0.0.0.0", port=7860)