SHOULD: Avoid gradient banding (use masks when needed)
"""
return html_content
# ========== Stateless Execution (backward compatible) ==========
@app.post("/execute", response_model=ExecutionResponse)
def execute_code(request: ExecutionRequest):
"""
Execute code in an isolated ephemeral container (stateless).
This is the original execution method - creates a fresh container,
executes code, and destroys the container immediately.
Note: Requires Docker to be available. On Hugging Face Spaces, Docker-in-Docker
is not supported, so this endpoint will not work.
"""
if not executor:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker is not available. Code execution requires Docker which is not supported on this platform. Please deploy to a Docker-capable platform for full functionality."
)
try:
logger.info(f"Stateless execution: {request.language} code")
result = executor.execute(request)
return result
except Exception as e:
logger.error(f"Execution failed: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Execution failed: {str(e)}"
)
# ========== Session Management ==========
@app.post("/sessions", response_model=SessionResponse, status_code=status.HTTP_201_CREATED)
def create_session(request: CreateSessionRequest):
"""
Create a new persistent VM-like session.
The session is a long-running container with persistent storage,
supporting file uploads and multiple code executions.
"""
if not session_manager:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker is not available. Session management requires Docker which is not supported on this platform. Please deploy to a Docker-capable platform for full functionality."
)
try:
logger.info(f"Creating new session with metadata: {request.metadata}")
session = session_manager.create_session(request)
return session
except RuntimeError as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
@app.get("/sessions", response_model=List[SessionResponse])
def list_sessions():
"""List all active sessions"""
if not session_manager:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker is not available. Session management requires Docker which is not supported on this platform. Please deploy to a Docker-capable platform for full functionality."
)
sessions = session_manager.list_sessions()
return sessions
@app.get("/sessions/{session_id}", response_model=SessionResponse)
def get_session(session_id: str = FastAPIPath(..., description="Session ID")):
"""Get session details by ID"""
if not session_manager:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker is not available. Session management requires Docker which is not supported on this platform. Please deploy to a Docker-capable platform for full functionality."
)
session = session_manager.get_session(session_id)
if not session:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Session {session_id} not found"
)
# Update file count
if file_manager:
try:
files = file_manager.list_files(session.container_id)
session.files_count = len(files)
except:
pass
return session
@app.delete("/sessions/{session_id}")
def destroy_session(session_id: str = FastAPIPath(..., description="Session ID")):
"""Destroy a session and cleanup all resources"""
if not session_manager:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker is not available. Session management requires Docker which is not supported on this platform. Please deploy to a Docker-capable platform for full functionality."
)
success = session_manager.destroy_session(session_id)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Session {session_id} not found"
)
return {"message": f"Session {session_id} destroyed successfully"}
# ========== File Operations ==========
@app.post("/sessions/{session_id}/files", response_model=FileUploadResponse)
async def upload_file(
session_id: str = FastAPIPath(..., description="Session ID"),
file: UploadFile = File(..., description="File to upload")
):
"""Upload a file to session workspace"""
if not session_manager or not file_manager:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker is not available. File operations require Docker which is not supported on this platform. Please deploy to a Docker-capable platform for full functionality."
)
# Get session
session = session_manager.get_session(session_id)
if not session:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Session {session_id} not found"
)
try:
# Read file data
file_data = await file.read()
# Upload to container
result = file_manager.upload_file(
container_id=session.container_id,
filename=file.filename,
file_data=file_data
)
# Update session activity
session_manager.update_activity(session_id)
return result
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
except Exception as e:
logger.error(f"File upload failed: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"File upload failed: {str(e)}"
)
@app.get("/sessions/{session_id}/files", response_model=List[FileInfo])
def list_files(session_id: str = FastAPIPath(..., description="Session ID")):
"""List files in session workspace"""
if not session_manager or not file_manager:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker is not available. File operations require Docker which is not supported on this platform. Please deploy to a Docker-capable platform for full functionality."
)
# Get session
session = session_manager.get_session(session_id)
if not session:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Session {session_id} not found"
)
try:
files = file_manager.list_files(session.container_id)
return files
except Exception as e:
logger.error(f"File listing failed: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"File listing failed: {str(e)}"
)
@app.get("/sessions/{session_id}/files/{filepath:path}")
def download_file(
session_id: str = FastAPIPath(..., description="Session ID"),
filepath: str = FastAPIPath(..., description="File path relative to workspace")
):
"""Download a file from session workspace"""
if not session_manager or not file_manager:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker is not available. File operations require Docker which is not supported on this platform. Please deploy to a Docker-capable platform for full functionality."
)
# Get session
session = session_manager.get_session(session_id)
if not session:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Session {session_id} not found"
)
try:
file_data = file_manager.download_file(session.container_id, filepath)
# Determine content type
import mimetypes
content_type, _ = mimetypes.guess_type(filepath)
return Response(
content=file_data,
media_type=content_type or "application/octet-stream",
headers={
"Content-Disposition": f"attachment; filename={filepath.split('/')[-1]}"
}
)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
except Exception as e:
logger.error(f"File download failed: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"File download failed: {str(e)}"
)
# ========== Execute in Session ==========
@app.post("/sessions/{session_id}/execute", response_model=ExecutionResponse)
def execute_in_session(
session_id: str = FastAPIPath(..., description="Session ID"),
request: ExecuteInSessionRequest = None
):
"""Execute code in an existing session (persistent state)"""
if not session_manager:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker is not available. Session management requires Docker which is not supported on this platform. Please deploy to a Docker-capable platform for full functionality."
)
# Get session
session = session_manager.get_session(session_id)
if not session:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Session {session_id} not found"
)
try:
import time
from docker.errors import DockerException
container = executor.client.containers.get(session.container_id)
runner_config = LanguageRunner.get_runner_config(request.language)
start_time = time.time()
# Execute command in running container
exec_result = container.exec_run(
cmd=runner_config["command"] + [request.code],
workdir=request.working_dir,
demux=True,
stream=False
)
execution_time = time.time() - start_time
# Parse output
stdout = exec_result.output[0].decode('utf-8', errors='replace') if exec_result.output[0] else ""
stderr = exec_result.output[1].decode('utf-8', errors='replace') if exec_result.output[1] else ""
# Update session activity
session_manager.update_activity(session_id)
return ExecutionResponse(
stdout=stdout,
stderr=stderr,
exit_code=exec_result.exit_code,
execution_time=round(execution_time, 3),
error=None if exec_result.exit_code == 0 else "Execution failed"
)
except DockerException as e:
logger.error(f"Docker error: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Execution failed: {str(e)}"
)
@app.post("/sessions/{session_id}/execute-file", response_model=ExecutionResponse)
def execute_file_in_session(
session_id: str = FastAPIPath(..., description="Session ID"),
request: ExecuteFileRequest = None
):
"""Execute an uploaded file in session"""
if not session_manager:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker is not available. Session management requires Docker which is not supported on this platform. Please deploy to a Docker-capable platform for full functionality."
)
# Get session
session = session_manager.get_session(session_id)
if not session:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Session {session_id} not found"
)
try:
import time
container = executor.client.containers.get(session.container_id)
runner_config = LanguageRunner.get_runner_config(request.language)
# Build command based on language
if request.language == Language.PYTHON:
cmd = ["python", request.filepath] + request.args
elif request.language == Language.JAVASCRIPT:
cmd = ["node", request.filepath] + request.args
elif request.language == Language.BASH:
cmd = ["bash", request.filepath] + request.args
else:
cmd = runner_config["command"] + [request.filepath] + request.args
start_time = time.time()
# Execute file
exec_result = container.exec_run(
cmd=cmd,
workdir="/workspace",
demux=True,
stream=False
)
execution_time = time.time() - start_time
# Parse output
stdout = exec_result.output[0].decode('utf-8', errors='replace') if exec_result.output[0] else ""
stderr = exec_result.output[1].decode('utf-8', errors='replace') if exec_result.output[1] else ""
# Update session activity
session_manager.update_activity(session_id)
return ExecutionResponse(
stdout=stdout,
stderr=stderr,
exit_code=exec_result.exit_code,
execution_time=round(execution_time, 3),
error=None if exec_result.exit_code == 0 else "Execution failed"
)
except Exception as e:
logger.error(f"File execution failed: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"File execution failed: {str(e)}"
)
@app.exception_handler(Exception)
async def global_exception_handler( request, exc):
"""Global exception handler"""
logger.error(f"Unhandled exception: {exc}", exc_info=True)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"error": "Internal server error",
"detail": str(exc)
}
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app:app",
host="0.0.0.0",
port=7860,
reload=False,
log_level="info"
)