nl2sql-copilot / app /main.py
Melika Kheirieh
fix(sqlite): unify adapter path resolution and load from env for stable pipeline execution
4c2cf14
raw
history blame
4.71 kB
import os
import time
from fastapi import FastAPI, Request, Response, HTTPException
from fastapi.responses import PlainTextResponse, RedirectResponse
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
from nl2sql.prom import REGISTRY
from app.routers import dev
try:
from dotenv import load_dotenv
load_dotenv()
except Exception:
pass
from app.routers import nl2sql
# ----------------------------------------------------------------------------
# App definition
# ----------------------------------------------------------------------------
application = FastAPI(
title="NL2SQL Copilot Prototype",
version=os.getenv("APP_VERSION", "0.1.0"),
description="Convert natural language to safe & verified SQL",
)
# Register only versioned API
application.include_router(nl2sql.router, prefix="/api/v1")
# Register Dev-only routes (only when APP_ENV=dev)
if os.getenv("APP_ENV", "dev").lower() == "dev":
application.include_router(dev.router, prefix="/api/v1")
# ----------------------------------------------------------------------------
# Prometheus Metrics Middleware
# ----------------------------------------------------------------------------
REQUEST_COUNT = Counter(
"http_requests_total",
"Total HTTP requests",
["path", "method", "status_code"],
registry=REGISTRY,
)
REQUEST_LATENCY = Histogram(
"http_request_latency_seconds",
"Request latency (seconds)",
["path", "method"],
registry=REGISTRY,
)
@application.middleware("http")
async def metrics_middleware(request: Request, call_next):
start = time.perf_counter()
response: Response = await call_next(request)
elapsed = time.perf_counter() - start
route = request.scope.get("route")
path = getattr(route, "path", None) or request.url.path
name = getattr(route, "name", None) or path
REQUEST_COUNT.labels(
path=name,
method=request.method,
status_code=str(getattr(response, "status_code", 500)),
).inc()
REQUEST_LATENCY.labels(path=name, method=request.method).observe(elapsed)
return response
# ----------------------------------------------------------------------------
# System Endpoints
# ----------------------------------------------------------------------------
@application.get("/healthz", response_class=PlainTextResponse, tags=["system"])
def healthz() -> str:
return "ok"
@application.get("/readyz", response_class=PlainTextResponse, tags=["system"])
def readyz() -> str:
mode = os.getenv("DB_MODE", "sqlite").lower()
try:
if mode == "postgres":
from adapters.db.postgres_adapter import PostgresAdapter
pg = PostgresAdapter(os.environ["POSTGRES_DSN"])
ping_fn = getattr(pg, "ping", None)
if callable(ping_fn):
ping_fn()
else:
from adapters.db.sqlite_adapter import SQLiteAdapter
sq = SQLiteAdapter(
os.getenv("DEFAULT_SQLITE_PATH", "data/Chinook_Sqlite.sqlite")
)
ping_fn = getattr(sq, "ping", None)
if callable(ping_fn):
ping_fn()
return "ready"
except Exception:
raise HTTPException(status_code=503, detail="not ready")
@application.get("/")
def root():
return {"status": "ok", "message": "NL2SQL Copilot API is running"}
@application.get("/health")
def health():
return {"status": "ok", "db": "connected", "llm": "reachable", "uptime_sec": 123.4}
@application.get("/metrics", tags=["system"])
def metrics():
data = generate_latest(REGISTRY)
return Response(content=data, media_type=CONTENT_TYPE_LATEST)
# ----------------------------------------------------------------------------
# Legacy Redirects (clean compatibility)
# ----------------------------------------------------------------------------
@application.api_route("/nl2sql", methods=["GET", "POST"])
async def legacy_nl2sql_redirect(request: Request):
return RedirectResponse(url="/api/v1/nl2sql", status_code=307)
@application.api_route(
"/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
)
async def legacy_catch_all(request: Request, path: str):
"""Redirect old root-level endpoints to versioned API."""
if path.startswith("api/v1"):
return RedirectResponse(url=f"/{path}", status_code=307)
return RedirectResponse(url=f"/api/v1/{path}", status_code=307)
# ----------------------------------------------------------------------------
# Backward-compatible alias for uvicorn
# ----------------------------------------------------------------------------
app = application
__all__ = ["application", "app"]