927437a8d8
Sistema FleetView para centralizar la flota de procesos Claude Code vivos en una sola ventana kitty + tmux (socket aislado -L fleet) con un panel TUI: - list_claude_fleet (+ tipo claude_fleet): escanea ~/.claude/sessions + goals + runtime, valida procesos vivos (anti-PID-reciclado), join por sessionId. - list_resumable_claudes (+ tipo resumable_claude): sesiones cerradas reanudables. - wrappers tmux: tmux_new_claude_window (con --resume), tmux_swap_window_into_console (preserva ancho del sidebar), tmux_map_claude_panes. - launch_kittyclaude: comando entrypoint; instala atajos alt+flechas/enter/n/0/k/r, mouse on, remain-on-exit off; fija el ancho del sidebar con hooks. - docs/capabilities/claude-fleet.md + entrada en el INDEX. Incluye ademas funciones datascience en progreso (excel/duckdb/postgres) y ajustes varios de docs e infra de otra sesion, agrupados aqui para no perderlos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
84 lines
4.7 KiB
Markdown
84 lines
4.7 KiB
Markdown
---
|
|
name: pg_query
|
|
kind: function
|
|
lang: py
|
|
domain: infra
|
|
version: "1.0.0"
|
|
purity: impure
|
|
signature: "def pg_query(dsn: str, sql: str, params: list = None, max_rows: int = 10000) -> dict"
|
|
description: "Ejecuta un SELECT contra PostgreSQL via psycopg2 y devuelve las filas como list[dict] sin lanzar. Abre la conexion con el DSN, marca la transaccion read-only (SET TRANSACTION READ ONLY) y usa RealDictCursor para que cada fila sea un dict columna->valor. Devuelve {status:'ok', columns, rows, row_count, truncated} en exito y {status:'error', error} en fallo (estilo duckdb_query_readonly). Usa parametros posicionales con el marcador %s. Trunca a max_rows para proteger memoria. Normaliza valores no JSON-serializables: date/datetime/time a isoformat(), Decimal a float, bytes/memoryview a base64, UUID a str. Cierra la conexion siempre en try/finally. Espejo de duckdb_query_readonly para Postgres. Depende de psycopg2 (2.9.x en python/.venv)."
|
|
tags: [postgres, postgresql, sql, query, readonly, infra]
|
|
uses_functions: []
|
|
uses_types: []
|
|
returns: []
|
|
returns_optional: false
|
|
error_type: "error_py_core"
|
|
imports: [base64, datetime, decimal, uuid, psycopg2]
|
|
params:
|
|
- name: dsn
|
|
desc: "Cadena de conexion PostgreSQL en formato postgresql://user:pass@host:port/dbname. Un DSN invalido o servidor inalcanzable devuelve {status:'error'} sin lanzar."
|
|
- name: sql
|
|
desc: "Sentencia SQL a ejecutar (pensada para SELECT). Usa el marcador %s para parametros posicionales (estilo psycopg2)."
|
|
- name: params
|
|
desc: "Lista de parametros posicionales para el SQL en orden. None (default) significa sin parametros. Pasar los valores aqui en vez de interpolarlos en el SQL evita inyeccion."
|
|
- name: max_rows
|
|
desc: "Numero maximo de filas a materializar en memoria (default 10000). Si la query produce mas, el resultado se trunca y truncated queda en True."
|
|
output: "dict. En exito: {status:'ok', columns:[str,...], rows:[{col:val,...},...], row_count:int, truncated:bool}; las filas son dicts (RealDictCursor). En error (sin lanzar): {status:'error', error:str}. Los valores estan normalizados a tipos JSON-serializables."
|
|
tested: true
|
|
tests: ["test_skip_sin_pg_test_dsn", "test_normaliza_tipos_no_serializables", "test_select_con_parametros_posicionales", "test_trunca_a_max_rows", "test_dsn_invalido_devuelve_status_error"]
|
|
test_file_path: "python/functions/infra/pg_query_test.py"
|
|
file_path: "python/functions/infra/pg_query.py"
|
|
---
|
|
|
|
## Ejemplo
|
|
|
|
```python
|
|
import sys, os
|
|
sys.path.insert(0, os.path.join("python", "functions"))
|
|
from infra.pg_query import pg_query
|
|
|
|
dsn = "postgresql://user:pass@localhost:5433/trends"
|
|
|
|
# SELECT con parametro posicional (nunca interpolar el valor en el SQL).
|
|
res = pg_query(
|
|
dsn,
|
|
"SELECT product, price FROM prices WHERE source = %s ORDER BY price DESC",
|
|
params=["amazon"],
|
|
max_rows=100,
|
|
)
|
|
print(res["status"]) # ok
|
|
print(res["columns"]) # ['product', 'price']
|
|
print(res["rows"][0]) # {'product': 'Widget X', 'price': 19.99}
|
|
print(res["truncated"]) # False
|
|
```
|
|
|
|
## Cuando usarla
|
|
|
|
Usala cuando necesites leer datos de Postgres y pasarlos a otro paso de una
|
|
composicion como dict serializable: inspeccionar una tabla, validar el resultado de
|
|
un pipeline de ingesta, alimentar un dashboard o report, o consultar tablas
|
|
materializadas. Es el espejo de `duckdb_query_readonly` para Postgres. Para escribir
|
|
usa `pg_insert_rows`, `pg_upsert` o `pg_apply_sql`.
|
|
|
|
## Gotchas
|
|
|
|
- Lectura real contra un servidor (impura). La transaccion se marca read-only con
|
|
`set_session(readonly=True)` y nunca se hace commit (rollback al final): cualquier
|
|
`INSERT`/`UPDATE`/`DELETE` en el SQL falla a nivel de servidor y vuelve como
|
|
`{status:'error', ...}`. NO es un sandbox de filesystem — read-only protege la
|
|
base, no impide leer datos sensibles si el SQL viene de un cliente no confiable.
|
|
- Inyeccion SQL: los **valores** van siempre por `params` con el marcador `%s`,
|
|
nunca interpolados en el string del SQL. Esta funcion NO valida ni parametriza
|
|
identificadores (nombres de tabla/columna): si necesitas un nombre de tabla
|
|
dinamico, validalo tu antes con `^[A-Za-z_][A-Za-z0-9_]*$`.
|
|
- `max_rows` protege la memoria: una query que devuelve millones de filas se trunca
|
|
a `max_rows` y marca `truncated=True`. Para todas las filas, pagina con
|
|
LIMIT/OFFSET o sube `max_rows` conscientemente.
|
|
- Valores no JSON-serializables se normalizan en la salida: date/datetime/time a
|
|
`isoformat()`, Decimal a float (posible perdida de precision frente al decimal
|
|
exacto), bytes/memoryview a base64 y UUID a str.
|
|
- Conexion nueva por llamada (sin pool). Para muchas consultas pequenas en bucle,
|
|
reusa una conexion fuera de esta funcion o agrupa el trabajo en una sola query.
|
|
- Nunca lanza: DSN invalido, servidor caido, SQL malformado o falta de psycopg2
|
|
vuelven como `{status:'error', error:str}`.
|