feat(infra): auto-commit con 56 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
"""Ejecuta un SELECT contra el service osint_db (DuckDB, 127.0.0.1:8771) por HTTP.
|
||||
|
||||
Funcion impura: hace un POST a ``{base_url}/api/query`` con el cuerpo JSON
|
||||
``{"sql": sql}`` y devuelve el resultado sin lanzar excepciones, siguiendo el estilo
|
||||
de pg_query / duckdb_query_readonly del registry: {status:'ok', ...} en exito y
|
||||
{status:'error', error:str} en fallo.
|
||||
|
||||
El osint_db es un FastAPI single-writer sobre DuckDB que es la fuente de verdad del
|
||||
proyecto osint. Su endpoint /api/query es estrictamente read-only (abre una conexion
|
||||
DuckDB read_only separada) y responde SIEMPRE con HTTP 200; el status real del
|
||||
dominio viaja en el cuerpo ({status, columns, rows, row_count, truncated} en exito,
|
||||
o {status:'error', error}). Esta funcion reenvia ese cuerpo tal cual cuando es ok y
|
||||
normaliza los errores de red (service caido, timeout, conexion rechazada) a un
|
||||
{status:'error', ...} con un mensaje claro, para no tumbar al llamante.
|
||||
|
||||
Solo stdlib (urllib, json): el wrapper es transporte puro, no reimplementa la logica
|
||||
del osint_db ni anade dependencias de runtime.
|
||||
"""
|
||||
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
|
||||
def query_osint_db(
|
||||
sql: str,
|
||||
base_url: str = "http://127.0.0.1:8771",
|
||||
timeout: int = 30,
|
||||
) -> dict:
|
||||
"""Ejecuta un SELECT contra el service osint_db por HTTP y devuelve un dict.
|
||||
|
||||
Args:
|
||||
sql: sentencia SQL a ejecutar. Pensada para SELECT read-only; el osint_db
|
||||
la corre con una conexion DuckDB en modo solo lectura, asi que una
|
||||
escritura fallara a nivel de service.
|
||||
base_url: URL base del service osint_db (default
|
||||
"http://127.0.0.1:8771"). Se le anade "/api/query" al hacer el POST.
|
||||
timeout: timeout por peticion en segundos (default 30). El osint_db es
|
||||
local (loopback): si tarda mas, mejor degradar que colgar al llamante.
|
||||
|
||||
Returns:
|
||||
dict. En exito reenvia el cuerpo del service:
|
||||
{status:'ok', columns:[str,...], rows:[{col:val, ...}, ...], row_count:int,
|
||||
truncated:bool}. En error (sin lanzar): {status:'error', error:str}. Si el
|
||||
service no es alcanzable (no arrancado, conexion rechazada, host caido) el
|
||||
error es "osint_db service not reachable on <url>: <detalle>".
|
||||
"""
|
||||
url = base_url.rstrip("/") + "/api/query"
|
||||
data = json.dumps({"sql": sql}).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=data,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
raw = resp.read().decode("utf-8")
|
||||
except urllib.error.HTTPError as exc:
|
||||
# El contrato del osint_db es 200 siempre; un HTTPError es anomalo. Intenta
|
||||
# leer el cuerpo (puede traer {status:error,...}); si no, error claro.
|
||||
try:
|
||||
body = json.loads(exc.read().decode("utf-8"))
|
||||
if isinstance(body, dict):
|
||||
return body
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"osint_db returned HTTP {exc.code} on {url}",
|
||||
}
|
||||
except (urllib.error.URLError, OSError) as exc:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"osint_db service not reachable on {url}: {exc}",
|
||||
}
|
||||
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
except ValueError as exc:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"osint_db returned non-JSON response on {url}: {exc}",
|
||||
}
|
||||
if not isinstance(parsed, dict):
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"osint_db returned unexpected JSON type on {url}",
|
||||
}
|
||||
return parsed
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
query = sys.argv[1] if len(sys.argv) > 1 else "SELECT COUNT(*) FROM personas"
|
||||
print(json.dumps(query_osint_db(query), ensure_ascii=False, indent=2))
|
||||
Reference in New Issue
Block a user