SPB Git

spb/findata

Public

public overview: background refresh (inline recompute took 40 s); HTML no-cache, hashed assets immutable

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Nov 2, 2026) parent f85ef07 Browse files

2 changed files +42 −9

modified services/api/src/findata_api/main.py +13 −0
@@ -52,6 +52,12 @@ class Observability(BaseHTTPMiddleware):
52 52 M.REQUESTS.labels(request.method, _route_label(route), str(response.status_code)).inc()
53 53 M.LATENCY.labels(_route_label(route)).observe(dt)
54 54 response.headers["X-Request-ID"] = rid
55 + if not route.startswith("/api/") and route != "/metrics":
56 + # static console: hashed assets are immutable, HTML must be revalidated so deployments show up at once
57 + if route.startswith("/_next/static/"):
58 + response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
59 + else:
60 + response.headers["Cache-Control"] = "no-cache"
55 61 response.headers["X-Content-Type-Options"] = "nosniff"
56 62 response.headers["X-Frame-Options"] = "DENY"
57 63 response.headers["Referrer-Policy"] = "no-referrer"
@@ -149,6 +155,13 @@ def create_app() -> FastAPI:
149 155 body, ctype = M.render()
150 156 return Response(body, media_type=ctype)
151 157
158 + @app.on_event("startup")
159 + def _warm() -> None:
160 + from findata_api.health import start_refresher as _h
161 + from findata_api.routers.public import start_refresher as _p
162 + _h()
163 + _p()
164 +
152 165 web = s.web_dist or Path(s.root) / "apps" / "web" / "out"
153 166 if web.exists():
154 167 app.mount("/", StaticFiles(directory=str(web), html=True), name="web")
modified services/api/src/findata_api/routers/public.py +29 −9
@@ -66,14 +66,34 @@ def _compute() -> dict[str, Any]:
66 66 }
67 67
68 68
69 +_started = False
70 +
71 +
72 +def _refresh_loop(period_s: float) -> None:
73 + while True:
74 + try:
75 + v = _compute()
76 + _cache.update(at=time.monotonic(), value=v)
77 + except Exception as e:
78 + _cache.setdefault("error", str(e)[:200])
79 + time.sleep(period_s)
80 +
81 +
82 +def start_refresher(period_s: float = 300.0) -> None:
83 + global _started
84 + if _started:
85 + return
86 + _started = True
87 + threading.Thread(target=_refresh_loop, args=(period_s,), name="public-overview-refresher", daemon=True).start()
88 +
89 +
69 90 @router.get("/overview")
70 91 def overview():
71 − now = time.monotonic()
72 − if _cache["value"] is not None and now - _cache["at"] < 120:
73 − return _cache["value"]
74 − with _lock:
75 − if _cache["value"] is not None and time.monotonic() - _cache["at"] < 120:
76 − return _cache["value"]
77 − v = _compute()
78 − _cache.update(at=time.monotonic(), value=v)
79 − return v
92 + """Served from a cache refreshed in the background every 5 minutes; the request path never runs the queries."""
93 + start_refresher()
94 + v = _cache["value"]
95 + if v is None:
96 + return {"generated_at": datetime.now(UTC).isoformat(), "status": "starting", "totals": None, "families": [], "datasets": [],
97 + "coverage": [], "options": {}, "fundamentals": {}, "instruments": {}, "freshness": {"jobs_24h": {}}, "storage": {},
98 + "backup": {}, "license": "FirstRate Data — private research use; raw bars and tables are not redistributed. Sign in for data access."}
99 + return v
80 100
Menu

Theme