Karan6933 commited on
Commit
e88ff52
·
verified ·
1 Parent(s): c08182e

Upload 6 files

Browse files
Files changed (6) hide show
  1. Dockerfile +23 -0
  2. README.md +1 -11
  3. requirements.txt +4 -0
  4. runner.py +143 -0
  5. server.py +101 -0
  6. verify_runner.py +46 -0
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ # Set environment variables
4
+ ENV PYTHONDONTWRITEBYTECODE=1 \
5
+ PYTHONUNBUFFERED=1
6
+
7
+ # Set the working directory
8
+ WORKDIR /app
9
+
10
+ # Copy requirements file
11
+ COPY requirements.txt .
12
+
13
+ # Install dependencies
14
+ RUN pip install --no-cache-dir -r requirements.txt
15
+
16
+ # Copy the application code
17
+ COPY . .
18
+
19
+ # Expose the port the app runs on
20
+ EXPOSE 8001
21
+
22
+ # Command to run the application
23
+ CMD ["python", "server.py"]
README.md CHANGED
@@ -1,11 +1 @@
1
- ---
2
- title: Python Enviroment
3
- emoji: 📚
4
- colorFrom: gray
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # GenAi-web-builder-pyhton-server
 
 
 
 
 
 
 
 
 
 
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ websockets
4
+ psutil
runner.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ import uuid
6
+ import venv
7
+ import shutil
8
+ from typing import Callable, Optional, Awaitable
9
+
10
+ class ProcessRunner:
11
+ def __init__(self, project_id: str, base_dir: str):
12
+ self.project_id = project_id
13
+ self.project_path = os.path.join(base_dir, project_id)
14
+ self.venv_path = os.path.join(self.project_path, "venv")
15
+ self.process: Optional[asyncio.subprocess.Process] = None
16
+ self.is_running = False
17
+
18
+ async def setup(self, files: list[dict]):
19
+ if not os.path.exists(self.project_path):
20
+ os.makedirs(self.project_path)
21
+
22
+ for file_info in files:
23
+ file_path = file_info.get("path")
24
+ content = file_info.get("content", "")
25
+
26
+ if not file_path:
27
+ continue
28
+
29
+ full_path = os.path.join(self.project_path, file_path)
30
+ os.makedirs(os.path.dirname(full_path), exist_ok=True)
31
+
32
+ with open(full_path, "w") as f:
33
+ f.write(content)
34
+
35
+ # Create venv if not exists
36
+ if not os.path.exists(self.venv_path):
37
+ venv.create(self.venv_path, with_pip=True)
38
+
39
+ async def install_dependencies(self, log_callback: Callable[[str], Awaitable[None]]):
40
+ req_file = os.path.join(self.project_path, "requirements.txt")
41
+ if not os.path.exists(req_file):
42
+ return
43
+
44
+ pip_path = os.path.join(self.venv_path, "bin", "pip")
45
+ if sys.platform == "win32":
46
+ pip_path = os.path.join(self.venv_path, "Scripts", "pip.exe")
47
+
48
+ cmd = [pip_path, "install", "-r", req_file]
49
+ await log_callback(f"Running: {' '.join(cmd)}\n")
50
+
51
+ process = await asyncio.create_subprocess_exec(
52
+ *cmd,
53
+ stdout=asyncio.subprocess.PIPE,
54
+ stderr=asyncio.subprocess.PIPE
55
+ )
56
+
57
+ async def stream_output(stream, prefix=""):
58
+ while True:
59
+ line = await stream.readline()
60
+ if not line:
61
+ break
62
+ await log_callback(line.decode())
63
+
64
+ await asyncio.gather(
65
+ stream_output(process.stdout),
66
+ stream_output(process.stderr)
67
+ )
68
+ await process.wait()
69
+
70
+ def detect_command(self):
71
+ # Look for entry points in the project path
72
+ python_path = os.path.join(self.venv_path, "bin", "python")
73
+ if sys.platform == "win32":
74
+ python_path = os.path.join(self.venv_path, "Scripts", "python.exe")
75
+
76
+ entry_point = "app.py"
77
+ if os.path.exists(os.path.join(self.project_path, "main.py")):
78
+ entry_point = "main.py"
79
+
80
+ with open(os.path.join(self.project_path, entry_point), "r") as f:
81
+ code = f.read()
82
+
83
+ if "FastAPI" in code:
84
+ uvicorn_path = os.path.join(self.venv_path, "bin", "uvicorn")
85
+ if sys.platform == "win32":
86
+ uvicorn_path = os.path.join(self.venv_path, "Scripts", "uvicorn.exe")
87
+ # Handle main vs app module
88
+ module = entry_point.replace(".py", "")
89
+ return [uvicorn_path, f"{module}:app", "--host", "0.0.0.0", "--port", "8000"]
90
+
91
+ if "Flask(" in code:
92
+ return [python_path, entry_point]
93
+
94
+ if "django" in code.lower():
95
+ return [python_path, "manage.py", "runserver", "0.0.0.0:8000"]
96
+
97
+ return [python_path, entry_point]
98
+
99
+ async def run(self, log_callback: Callable[[str], Awaitable[None]]):
100
+ if self.process:
101
+ try:
102
+ self.process.terminate()
103
+ await self.process.wait()
104
+ except:
105
+ pass
106
+
107
+ cmd = self.detect_command()
108
+ await log_callback(f"Starting process: {' '.join(cmd)}\n")
109
+
110
+ self.process = await asyncio.create_subprocess_exec(
111
+ *cmd,
112
+ stdout=asyncio.subprocess.PIPE,
113
+ stderr=asyncio.subprocess.PIPE,
114
+ cwd=self.project_path
115
+ )
116
+ self.is_running = True
117
+
118
+ async def stream_output(stream):
119
+ while True:
120
+ line = await stream.readline()
121
+ if not line:
122
+ break
123
+ await log_callback(line.decode())
124
+
125
+ # Run streams in background
126
+ asyncio.create_task(stream_output(self.process.stdout))
127
+ asyncio.create_task(stream_output(self.process.stderr))
128
+
129
+ async def stop(self):
130
+ if self.process:
131
+ try:
132
+ self.process.terminate()
133
+ await self.process.wait()
134
+ except ProcessLookupError:
135
+ pass
136
+ except Exception as e:
137
+ print(f"Error stopping process: {e}")
138
+ self.process = None
139
+ self.is_running = False
140
+
141
+ def cleanup(self):
142
+ if os.path.exists(self.project_path):
143
+ shutil.rmtree(self.project_path)
server.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ import json
4
+ import asyncio
5
+ import os
6
+ import uuid
7
+ import shutil
8
+ from runner import ProcessRunner
9
+
10
+ app = FastAPI()
11
+
12
+ app.add_middleware(
13
+ CORSMiddleware,
14
+ allow_origins=["*"],
15
+ allow_credentials=True,
16
+ allow_methods=["*"],
17
+ allow_headers=["*"],
18
+ )
19
+
20
+ PROJECTS_DIR = os.path.join(os.path.dirname(__file__), "projects")
21
+ if not os.path.exists(PROJECTS_DIR):
22
+ os.makedirs(PROJECTS_DIR)
23
+
24
+ active_runners = {}
25
+
26
+ def cleanup_old_projects():
27
+ try:
28
+ projects = []
29
+ for name in os.listdir(PROJECTS_DIR):
30
+ path = os.path.join(PROJECTS_DIR, name)
31
+ if os.path.isdir(path):
32
+ mtime = os.stat(path).st_mtime
33
+ projects.append((mtime, name, path))
34
+
35
+ # Sort by modification time, newest first
36
+ projects.sort(reverse=True)
37
+
38
+ # Keep the 4 newest, delete the rest so we have max 5 after creating a new one
39
+ for _, name, path in projects[4:]:
40
+ if name not in active_runners:
41
+ shutil.rmtree(path, ignore_errors=True)
42
+ except Exception as e:
43
+ print(f"Error cleaning up projects: {e}")
44
+
45
+ @app.websocket("/ws/run")
46
+ async def websocket_endpoint(websocket: WebSocket):
47
+ await websocket.accept()
48
+ cleanup_old_projects()
49
+ project_id = str(uuid.uuid4())
50
+ runner = ProcessRunner(project_id, PROJECTS_DIR)
51
+ active_runners[project_id] = runner
52
+
53
+ try:
54
+ while True:
55
+ data = await websocket.receive_text()
56
+ payload = json.loads(data)
57
+
58
+ files = payload.get("files", [])
59
+
60
+ if not files:
61
+ await websocket.send_text(json.dumps({"type": "error", "message": "No files provided"}))
62
+ continue
63
+
64
+ async def log_callback(msg: str):
65
+ await websocket.send_text(json.dumps({"type": "log", "content": msg}))
66
+
67
+ await log_callback(f"✦ Preparing environment for project {project_id}...\n")
68
+ await runner.setup(files)
69
+
70
+ await log_callback("✦ Synchronizing dependencies...\n")
71
+ await runner.install_dependencies(log_callback)
72
+
73
+ await log_callback("🚀 Launching application...\n")
74
+ await runner.run(log_callback)
75
+
76
+ # Keep connection open and monitor for timeout or closure
77
+ # In a real production app, we'd have more complex process management here
78
+ try:
79
+ # Wait for disconnect or some signal from client
80
+ while True:
81
+ await asyncio.wait_for(websocket.receive_text(), timeout=3600)
82
+ except asyncio.TimeoutError:
83
+ await log_callback("\n[System] Session heartbeat timeout (1 hour).\n")
84
+
85
+ except WebSocketDisconnect:
86
+ print(f"Client disconnected: {project_id}")
87
+ except Exception as e:
88
+ print(f"Error: {e}")
89
+ try:
90
+ await websocket.send_text(json.dumps({"type": "error", "message": str(e)}))
91
+ except:
92
+ pass
93
+ finally:
94
+ await runner.stop()
95
+ # runner.cleanup() # Optional: keep files for debugging or clean them up
96
+ if project_id in active_runners:
97
+ del active_runners[project_id]
98
+
99
+ if __name__ == "__main__":
100
+ import uvicorn
101
+ uvicorn.run(app, host="0.0.0.0", port=8001)
verify_runner.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import websockets
3
+ import json
4
+
5
+ async def test_runner():
6
+ uri = "ws://localhost:8001/ws/run"
7
+ async with websockets.connect(uri) as websocket:
8
+ code = """
9
+ from flask import Flask
10
+ app = Flask(__name__)
11
+
12
+ @app.route('/')
13
+ def hello():
14
+ return "Hello from Backend Python Runner!"
15
+
16
+ if __name__ == '__main__':
17
+ app.run(host='0.0.0.0', port=5000)
18
+ """
19
+ payload = {
20
+ "code": code,
21
+ "requirements": "flask"
22
+ }
23
+
24
+ print("Sending payload...")
25
+ await websocket.send(json.dumps(payload))
26
+
27
+ print("Waiting for logs...")
28
+ try:
29
+ while True:
30
+ response = await websocket.recv()
31
+ data = json.loads(response)
32
+
33
+ if data["type"] == "log":
34
+ print(f"LOG: {data['content']}", end="")
35
+ # Check if Flask started
36
+ if "Running on http" in data["content"]:
37
+ print("\n[SUCCESS] Flask is running!")
38
+ break
39
+ elif data["type"] == "error":
40
+ print(f"ERROR: {data['message']}")
41
+ break
42
+ except Exception as e:
43
+ print(f"Exception: {e}")
44
+
45
+ if __name__ == "__main__":
46
+ asyncio.run(test_runner())