From bf2cc519fe26e67f226a3923c17d378627846692 Mon Sep 17 00:00:00 2001 From: Shroominic Date: Thu, 1 Aug 2024 19:07:44 +0200 Subject: [PATCH] one bug further lol --- Dockerfile | 2 +- src/codeboxapi/api.py | 33 +++++++++++++++++++++------------ src/codeboxapi/docker.py | 2 +- src/codeboxapi/remote.py | 23 +++-------------------- 4 files changed, 26 insertions(+), 34 deletions(-) diff --git a/Dockerfile b/Dockerfile index c6a9962..1a8fb86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ghcr.io/astral-sh/uv as uv -FROM --platform=arm64 python:3.11 as build +FROM --platform=amd64 python:3.11 as build ENV VIRTUAL_ENV=/.venv PATH="/.venv/bin:$PATH" diff --git a/src/codeboxapi/api.py b/src/codeboxapi/api.py index df3d8a3..3c6e7d3 100644 --- a/src/codeboxapi/api.py +++ b/src/codeboxapi/api.py @@ -2,9 +2,10 @@ from contextlib import asynccontextmanager from datetime import datetime, timedelta from os import getenv +from tempfile import SpooledTemporaryFile from typing import AsyncGenerator, Literal -from fastapi import Depends, FastAPI, HTTPException, UploadFile +from fastapi import Body, Depends, FastAPI, HTTPException, UploadFile from fastapi.responses import StreamingResponse from pydantic import BaseModel @@ -18,8 +19,9 @@ @asynccontextmanager async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]: async def timeout(): - timeout_secs = float(getenv("CODEBOX_TIMEOUT", "900")) - while last_interaction + timedelta(seconds=timeout_secs) > datetime.utcnow(): + if (_timeout := getenv("CODEBOX_TIMEOUT", "90")).lower() == "none": + return + while last_interaction + timedelta(seconds=float(_timeout)) > datetime.utcnow(): await asyncio.sleep(1) exit(0) @@ -28,18 +30,14 @@ async def timeout(): t.cancel() -app = FastAPI(title="Codebox API", lifespan=lifespan) - - async def get_codebox() -> AsyncGenerator[LocalBox, None]: global codebox, last_interaction last_interaction = datetime.utcnow() yield codebox -@app.get("/") -async def healthcheck() -> dict[str, str]: - return {"status": "ok"} +app = FastAPI(title="Codebox API", lifespan=lifespan) +app.get("/")(lambda: {"status": "ok"}) class ExecBody(BaseModel): @@ -62,7 +60,7 @@ async def event_stream() -> AsyncGenerator[str, None]: return StreamingResponse(event_stream()) -@app.get("/download/{file_name}") +@app.get("/files/download/{file_name}") async def download( file_name: str, timeout: int | None = None, @@ -71,7 +69,7 @@ async def download( return StreamingResponse(codebox.astream_download(file_name, timeout)) -@app.post("/upload") +@app.post("/files/upload") async def upload( file: UploadFile, timeout: int | None = None, @@ -79,13 +77,24 @@ async def upload( ) -> "CodeBoxFile": if not file.filename: raise HTTPException(status_code=400, detail="A file name is required") + if isinstance(file.file, SpooledTemporaryFile): + file.file = file.file return await codebox.aupload(file.filename, file.file, timeout) +@app.post("/code/execute") +async def deprecated_exec( + body: dict = Body(), codebox: LocalBox = Depends(get_codebox) +) -> dict: + """deprecated: use /exec instead""" + ex = await codebox.aexec(body["properties"]["code"]) + return {"properties": {"stdout": ex.text, "stderr": ex.errors, "result": ex.text}} + + def serve(): import uvicorn - uvicorn.run(app, host="0.0.0.0", port=getenv("CODEBOX_PORT", 8069)) + uvicorn.run(app, host="0.0.0.0", port=8000) if __name__ == "__main__": diff --git a/src/codeboxapi/docker.py b/src/codeboxapi/docker.py index a7bd01d..ac42c09 100644 --- a/src/codeboxapi/docker.py +++ b/src/codeboxapi/docker.py @@ -43,7 +43,7 @@ def __init__( "-e", f"CODEBOX_TIMEOUT={timeout}", "-p", - f"{self.port}:8069", + f"{self.port}:8000", image, ], check=True, diff --git a/src/codeboxapi/remote.py b/src/codeboxapi/remote.py index 8388bfb..e28edf9 100644 --- a/src/codeboxapi/remote.py +++ b/src/codeboxapi/remote.py @@ -100,21 +100,6 @@ async def astream_exec( async for c in self.astream_exec(code, kernel, timeout, cwd): yield c - def upload( - self, - remote_file_path: str, - content: BinaryIO | bytes | str, - timeout: float | None = None, - ) -> CodeBoxFile: - if isinstance(content, str): - content = content.encode("utf-8") - response = self.client.post( - url="/upload", - files={"file": (remote_file_path, content)}, - timeout=timeout, - ) - return CodeBoxFile(**response.json()) - async def aupload( self, file_name: str, @@ -124,7 +109,7 @@ async def aupload( if isinstance(content, str): content = content.encode("utf-8") response = await self.aclient.post( - url="/upload", + url="/files/upload", files={"file": (file_name, content)}, timeout=timeout, ) @@ -137,9 +122,8 @@ def stream_download( ) -> Generator[bytes, None, None]: with self.client.stream( method="GET", - url="/download", + url=f"/files/download/{remote_file_path}", timeout=timeout, - params={"file_name": remote_file_path}, ) as response: for chunk in response.iter_bytes(): yield chunk @@ -151,9 +135,8 @@ async def astream_download( ) -> AsyncGenerator[bytes, None]: async with self.aclient.stream( method="GET", - url="/download", + url=f"/files/download/{remote_file_path}", timeout=timeout, - params={"file_name": remote_file_path}, ) as response: async for chunk in response.aiter_bytes(): yield chunk