22from contextlib import asynccontextmanager
33from datetime import datetime , timedelta
44from os import getenv
5+ from tempfile import SpooledTemporaryFile
56from typing import AsyncGenerator , Literal
67
7- from fastapi import Depends , FastAPI , HTTPException , UploadFile
8+ from fastapi import Body , Depends , FastAPI , HTTPException , UploadFile
89from fastapi .responses import StreamingResponse
910from pydantic import BaseModel
1011
1819@asynccontextmanager
1920async def lifespan (_ : FastAPI ) -> AsyncGenerator [None , None ]:
2021 async def timeout ():
21- timeout_secs = float (getenv ("CODEBOX_TIMEOUT" , "900" ))
22- while last_interaction + timedelta (seconds = timeout_secs ) > datetime .utcnow ():
22+ if (_timeout := getenv ("CODEBOX_TIMEOUT" , "90" )).lower () == "none" :
23+ return
24+ while last_interaction + timedelta (seconds = float (_timeout )) > datetime .utcnow ():
2325 await asyncio .sleep (1 )
2426 exit (0 )
2527
@@ -28,18 +30,14 @@ async def timeout():
2830 t .cancel ()
2931
3032
31- app = FastAPI (title = "Codebox API" , lifespan = lifespan )
32-
33-
3433async def get_codebox () -> AsyncGenerator [LocalBox , None ]:
3534 global codebox , last_interaction
3635 last_interaction = datetime .utcnow ()
3736 yield codebox
3837
3938
40- @app .get ("/" )
41- async def healthcheck () -> dict [str , str ]:
42- return {"status" : "ok" }
39+ app = FastAPI (title = "Codebox API" , lifespan = lifespan )
40+ app .get ("/" )(lambda : {"status" : "ok" })
4341
4442
4543class ExecBody (BaseModel ):
@@ -62,7 +60,7 @@ async def event_stream() -> AsyncGenerator[str, None]:
6260 return StreamingResponse (event_stream ())
6361
6462
65- @app .get ("/download/{file_name}" )
63+ @app .get ("/files/ download/{file_name}" )
6664async def download (
6765 file_name : str ,
6866 timeout : int | None = None ,
@@ -71,21 +69,32 @@ async def download(
7169 return StreamingResponse (codebox .astream_download (file_name , timeout ))
7270
7371
74- @app .post ("/upload" )
72+ @app .post ("/files/ upload" )
7573async def upload (
7674 file : UploadFile ,
7775 timeout : int | None = None ,
7876 codebox : LocalBox = Depends (get_codebox ),
7977) -> "CodeBoxFile" :
8078 if not file .filename :
8179 raise HTTPException (status_code = 400 , detail = "A file name is required" )
80+ if isinstance (file .file , SpooledTemporaryFile ):
81+ file .file = file .file
8282 return await codebox .aupload (file .filename , file .file , timeout )
8383
8484
85+ @app .post ("/code/execute" )
86+ async def deprecated_exec (
87+ body : dict = Body (), codebox : LocalBox = Depends (get_codebox )
88+ ) -> dict :
89+ """deprecated: use /exec instead"""
90+ ex = await codebox .aexec (body ["properties" ]["code" ])
91+ return {"properties" : {"stdout" : ex .text , "stderr" : ex .errors , "result" : ex .text }}
92+
93+
8594def serve ():
8695 import uvicorn
8796
88- uvicorn .run (app , host = "0.0.0.0" , port = getenv ( "CODEBOX_PORT" , 8069 ) )
97+ uvicorn .run (app , host = "0.0.0.0" , port = 8000 )
8998
9099
91100if __name__ == "__main__" :
0 commit comments