from fastapi import FastAPI, Request, HTTPException from fastapi.responses import StreamingResponse, HTMLResponse import subprocess import httpx app = FastAPI(title="Market-Data Observatory") # Async HTTP client for proxying to ClickHouse's internal HTTP interface ch_client = httpx.AsyncClient(base_url="http://127.0.0.1:8123", timeout=60.0) # ── Refresh endpoint (git pull) ── @app.post("/refresh") def refresh(): try: result = subprocess.check_output( "cd /app/data/ohlc_data && git pull", shell=True, stderr=subprocess.STDOUT ) return {"status": "updated", "details": result.decode().strip()} except subprocess.CalledProcessError as e: msg = e.output.decode() if e.output else str(e) raise HTTPException(status_code=500, detail={"error": "Refresh failed", "message": msg}) # ── Health check ── @app.get("/health") async def health(): try: r = await ch_client.get("/ping") return {"clickhouse": r.text.strip(), "status": "ok"} except Exception as e: raise HTTPException(status_code=503, detail={"status": "unhealthy", "error": str(e)}) # ── Catch-all reverse proxy to ClickHouse ── # This gives you /play, /dashboard, native HTTP API, everything. @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"]) async def proxy_to_clickhouse(request: Request, path: str): url = httpx.URL(path=f"/{path}", query=request.url.query.encode("utf-8")) # Forward headers, strip hop-by-hop headers = dict(request.headers) for h in ("host", "content-length", "transfer-encoding"): headers.pop(h, None) try: body = await request.body() req = ch_client.build_request( method=request.method, url=url, headers=headers, content=body, ) r = await ch_client.send(req, stream=True) # Pass through ClickHouse response headers resp_headers = dict(r.headers) for h in ("content-length", "content-encoding", "transfer-encoding"): resp_headers.pop(h, None) return StreamingResponse( r.aiter_raw(), status_code=r.status_code, headers=resp_headers, ) except httpx.RequestError as exc: raise HTTPException(status_code=502, detail=f"ClickHouse proxy error: {exc}")