"""Small web adapters, owned by this project; synchronous work retains its slot.""" import threading from contextlib import contextmanager from pathlib import Path from fastapi import HTTPException from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles class Busy(HTTPException): def __init__(self): super().__init__( 429, "The workspace is busy. Wait a moment and try again.", headers={"Retry-After": "5"} ) class Admission: def __init__(self, active, waiting): self.capacity = active self.maximum = active + waiting self.total = 0 self.running = 0 self.condition = threading.Condition() @contextmanager def slot(self): with self.condition: if self.total >= self.maximum: raise Busy() self.total += 1 self.condition.notify_all() if not self.condition.wait_for(lambda: self.running < self.capacity, timeout=60): self.total -= 1 self.condition.notify_all() raise Busy() self.running += 1 try: yield finally: with self.condition: self.running -= 1 self.total -= 1 self.condition.notify_all() def limit_body(app, maximum): @app.middleware("http") async def bounded_body(request, call_next): if request.method in {"POST", "PUT"}: data = bytearray() async for chunk in request.stream(): data.extend(chunk) if len(data) > maximum: return JSONResponse( {"detail": "Request exceeds the upload limit."}, status_code=413 ) request._body = bytes(data) response = await call_next(request) response.headers["X-Content-Type-Options"] = "nosniff" if request.url.path.startswith("/api/"): response.headers["Cache-Control"] = "no-store" return response def mount_frontend(app): static = Path(__file__).parent / "static" if not (static / "index.html").exists(): @app.get("/") def missing_build(): return JSONResponse( {"detail": "Frontend not built. Run npm ci and npm run build in frontend/."}, status_code=503, ) else: app.mount("/", StaticFiles(directory=static, html=True), name="frontend")