88
99from app .app_configs import get_settings
1010from app .models .schemas import (
11+ CreateSessionRequest ,
12+ CreateSessionResponse ,
13+ ExecuteFile ,
1114 ExecuteRequest ,
1215 ExecuteResponse ,
1316 FileMetadataResponse ,
1922 WorkspaceFile ,
2023)
2124from app .services .executor_base import EntryKind , StreamChunk , StreamResult , WorkspaceEntry
22- from app .services .executor_factory import execute_python , execute_python_streaming
25+ from app .services .executor_factory import execute_python , execute_python_streaming , get_executor
2326from app .services .file_storage import FileStorageService
2427
2528router = APIRouter ()
@@ -46,8 +49,8 @@ def _validate_timeout(req: ExecuteRequest) -> None:
4649 )
4750
4851
49- def _stage_request_files (
50- req : ExecuteRequest ,
52+ def _resolve_uploaded_files (
53+ files : list [ ExecuteFile ] ,
5154 storage : FileStorageService ,
5255) -> tuple [list [tuple [str , bytes ]], dict [str , bytes ]]:
5356 """Resolve uploaded file IDs into content for the executor.
@@ -56,7 +59,7 @@ def _stage_request_files(
5659 """
5760 staged_files : list [tuple [str , bytes ]] = []
5861 input_files_map : dict [str , bytes ] = {}
59- for file in req . files :
62+ for file in files :
6063 try :
6164 content , _ = storage .get_file (file .file_id )
6265 except FileNotFoundError as exc :
@@ -69,6 +72,14 @@ def _stage_request_files(
6972 return staged_files , input_files_map
7073
7174
75+ def _stage_request_files (
76+ req : ExecuteRequest ,
77+ storage : FileStorageService ,
78+ ) -> tuple [list [tuple [str , bytes ]], dict [str , bytes ]]:
79+ """Resolve uploaded file IDs into content for the executor."""
80+ return _resolve_uploaded_files (req .files , storage )
81+
82+
7283def _save_workspace_files (
7384 entries : tuple [WorkspaceEntry , ...],
7485 input_files_map : dict [str , bytes ],
@@ -248,3 +259,57 @@ def delete_file(file_id: str) -> Response:
248259 )
249260
250261 return Response (status_code = status .HTTP_204_NO_CONTENT )
262+
263+
264+ @router .post (
265+ "/sessions" ,
266+ response_model = CreateSessionResponse ,
267+ status_code = status .HTTP_201_CREATED ,
268+ )
269+ def create_session (req : CreateSessionRequest ) -> CreateSessionResponse :
270+ """Create a long-lived code-executor pod.
271+
272+ The session must be torn down explicitly via DELETE /v1/sessions/{id}.
273+ """
274+ settings = get_settings ()
275+ storage = get_file_storage ()
276+ staged_files , _ = _resolve_uploaded_files (req .files , storage )
277+
278+ try :
279+ info = get_executor ().create_session (
280+ files = staged_files ,
281+ cpu_time_limit_sec = settings .cpu_time_limit_sec ,
282+ memory_limit_mb = settings .memory_limit_mb ,
283+ )
284+ except NotImplementedError as exc :
285+ raise HTTPException (
286+ status_code = status .HTTP_501_NOT_IMPLEMENTED ,
287+ detail = str (exc ),
288+ ) from exc
289+ except ValueError as exc :
290+ raise HTTPException (
291+ status_code = status .HTTP_422_UNPROCESSABLE_ENTITY ,
292+ detail = str (exc ),
293+ ) from exc
294+
295+ return CreateSessionResponse (session_id = info .session_id )
296+
297+
298+ @router .delete ("/sessions/{session_id}" , status_code = status .HTTP_204_NO_CONTENT )
299+ def delete_session (session_id : str ) -> Response :
300+ """Tear down a session pod by ID."""
301+ try :
302+ deleted = get_executor ().delete_session (session_id )
303+ except NotImplementedError as exc :
304+ raise HTTPException (
305+ status_code = status .HTTP_501_NOT_IMPLEMENTED ,
306+ detail = str (exc ),
307+ ) from exc
308+
309+ if not deleted :
310+ raise HTTPException (
311+ status_code = status .HTTP_404_NOT_FOUND ,
312+ detail = f"Session '{ session_id } ' not found" ,
313+ )
314+
315+ return Response (status_code = status .HTTP_204_NO_CONTENT )
0 commit comments