vanna-clubpetro/server.py
Dalton Alvarenga f4a2ee44ca
All checks were successful
CD / build (pull_request) Successful in 5m2s
[lab] chore(promotion): prepara serviço para gates lab -> homolog
Adequa o repo ao guia de promoção lab -> homolog mantendo Python (Gate 0 via
stackException). Cumpre os gates aplicáveis:

- promotion-manifest.yaml: stackException (Python/Vanna), Gate B N/A, Gate D/G
  declarados, capacidade e deltas.
- k8s/hml/: overlay homolog — secret DB dedicado (vanna-clubpetro-db, Gate G),
  probes httpGet /health (Gate E), resources.requests, replicas:1, non-root
  securityContext; host homologation.clubpetro.com (sem host de lab).
- server.py: rota GET /health (alvo das probes).
- Dockerfile: usuário non-root uid/gid 10001 + HOME/cache graváveis (Gate E).
- .env.example: remove literal lab.clubpetro.com do CORS (Gate D).
- k8s/ flat movido para k8s/lab/ (simetria lab/hml); cd.yml aplica k8s/lab/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-11 18:50:22 -03:00

102 lines
3.3 KiB
Python

"""FastAPI server: oficial VannaFastAPIServer + estáticos do web component."""
from __future__ import annotations
import os
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from vanna.servers.fastapi import VannaFastAPIServer
import csv_cleanup
from agent import RequestContextUserResolver, build_agent
from chat_filter import FilteringChatHandler
from events_sink import EventSink
def _build_app():
agent = build_agent(user_resolver=RequestContextUserResolver())
cors_origins = [
o.strip()
for o in os.environ.get("VANNA_CORS_ORIGINS", "*").split(",")
if o.strip()
] or ["*"]
server = VannaFastAPIServer(
agent,
config={
"cors": {
"allow_origins": cors_origins,
"allow_methods": ["*"],
"allow_headers": ["*"],
"allow_credentials": True,
},
"api_base_url": "",
},
)
server.chat_handler = FilteringChatHandler(agent, event_sink=EventSink())
fastapi_app = server.create_app()
here = os.path.dirname(os.path.abspath(__file__))
dist = os.path.join(here, "vanna", "frontends", "webcomponent", "dist")
if os.path.isdir(dist):
fastapi_app.mount("/static", StaticFiles(directory=dist), name="static")
@fastapi_app.get("/health")
async def health():
# Alvo das probes de liveness/readiness (Gate E). Leve, sem tocar
# ClickHouse/LLM — só sinaliza que o processo subiu e serve HTTP.
return {"status": "ok"}
@fastapi_app.get("/vanna-theme.css")
async def vanna_theme():
path = os.path.join(here, "static", "vanna-theme.css")
return FileResponse(path, media_type="text/css")
@fastapi_app.get("/vanna-embed-bootstrap.js")
async def vanna_embed_bootstrap():
path = os.path.join(here, "static", "vanna-embed-bootstrap.js")
return FileResponse(path, media_type="application/javascript")
@fastapi_app.get("/clubpetro-logo.png")
async def clubpetro_logo():
path = os.path.join(here, "static", "clubpetro-logo.png")
return FileResponse(path, media_type="image/png")
@fastapi_app.get("/clubpetro-logo.svg")
async def clubpetro_logo_svg():
path = os.path.join(here, "static", "clubpetro-logo.svg")
return FileResponse(path, media_type="image/svg+xml")
@fastapi_app.get("/dashboard-bg.png")
async def dashboard_bg():
path = os.path.join(here, "static", "dashboard-bg.png")
return FileResponse(path, media_type="image/png")
@fastapi_app.get("/embed-demo.html", response_class=HTMLResponse)
async def embed_demo():
path = os.path.join(here, "static", "embed-demo.html")
with open(path, "r", encoding="utf-8") as f:
html = f.read()
return (
html
.replace("__PROGRAM_ID__", os.environ.get("RLS_PROGRAM_ID", ""))
.replace("__STORE_ID__", os.environ.get("RLS_STORE_ID", ""))
.replace("__USER_ID__", os.environ.get("RLS_USER_ID", ""))
)
return fastapi_app
app = _build_app()
@app.on_event("startup")
async def _csv_cleanup_startup() -> None:
await csv_cleanup.startup()
@app.on_event("shutdown")
async def _csv_cleanup_shutdown() -> None:
await csv_cleanup.shutdown()