# retoor <retoor@molodetz.nl>
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from devplacepy.database import get_int_setting
from devplacepy.schemas import NlQueryOut
from devplacepy.services.dbapi import nl2sql
from devplacepy.services.dbapi.validate import run_select
from ._shared import assert_table, error, read_body, require_dbapi_caller
router = APIRouter()
DEFAULT_MAX_ROWS = 5000
def _truthy(value) -> bool:
return str(value).strip().lower() in ("1", "true", "yes", "on")
@router.post("/nl")
async def dbapi_nl(request: Request):
caller = require_dbapi_caller(request)
body = await read_body(request)
question = str(body.get("question", "")).strip()
table = str(body.get("table", "")).strip()
if not question or not table:
return error(400, "Provide both 'question' and 'table'.")
assert_table(table)
apply_soft_delete = _truthy(body.get("apply_soft_delete", True))
dialect = str(body.get("dialect", "sqlite")).strip() or "sqlite"
execute = _truthy(body.get("execute", False))
design = await nl2sql.design_query(
question,
table,
apply_soft_delete=apply_soft_delete,
dialect=dialect,
api_key=caller.api_key,
)
out = NlQueryOut(**design.as_dict())
if execute and design.valid:
max_rows = max(1, get_int_setting("dbapi_max_rows", DEFAULT_MAX_ROWS))
rows, truncated = run_select(design.sql, max_rows)
out.executed = True
out.rows = rows
out.row_count = len(rows)
out.truncated = truncated
_audit_nl(request, caller, table, design.sql, design.valid)
return JSONResponse(out.model_dump(mode="json"))
def _audit_nl(request, caller, table, sql, valid):
from devplacepy.services.audit import record as audit
audit.record(
request,
"database.nl.design",
target_type=table,
summary=f"{caller.username or caller.kind} designed a query on {table}",
metadata={"table": table, "sql": sql[:500], "valid": valid, "caller": caller.kind},
)