gaurabdas's picture
Deploy DocVerify fictional document-review demo
71b0992 verified
Raw History Blame Contribute Delete
2.59 kB
"""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")