bha-Search / app /bootstrap_api.py
vomebook's picture
Add generation-scoped first-screen bootstrap
f382dd4 verified
Raw
History Blame Contribute Delete
2.27 kB
import hashlib
import json
import threading
from typing import Any
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from . import runtime, search_api
from .search_logic import SearchRequest
from .storage_lock import serving_lock
router = APIRouter()
bootstrap_cache: tuple[tuple[Any, ...], dict[str, Any]] | None = None
bootstrap_lock = threading.Lock()
def generation_id(generation: tuple[Any, ...]) -> str:
return hashlib.sha256(repr(generation).encode()).hexdigest()[:16]
@router.get("/api/bootstrap")
def bootstrap():
global bootstrap_cache
with bootstrap_lock:
generation = runtime.serving_generation_token()
if runtime.index_status() == "ready" and bootstrap_cache and bootstrap_cache[0] == generation:
return JSONResponse(bootstrap_cache[1], headers={"Cache-Control": "no-store"})
if not runtime.serving_generation_ready():
return JSONResponse({"error": "serving generation is not ready"}, status_code=503)
for _attempt in range(2):
generation = runtime.serving_generation_token()
search_response = search_api.search(SearchRequest())
if not isinstance(search_response, JSONResponse) or search_response.status_code != 200:
return JSONResponse({"error": "default search is unavailable"}, status_code=503)
search_payload = json.loads(search_response.body)
with serving_lock():
sources = runtime.cached_sources_payload()
facets = {
kind: runtime.cached_facet_payload(kind, 1, 200, "")
for kind in ("source", "author", "tag")
}
if runtime.index_status() != "ready" or runtime.serving_generation_token() != generation:
continue
payload = {
"generation": generation_id(generation),
"search": search_payload,
"sources": sources,
"facets": facets,
}
bootstrap_cache = (generation, payload)
return JSONResponse(payload, headers={"Cache-Control": "no-store"})
return JSONResponse({"error": "serving generation changed during bootstrap"}, status_code=503)