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>
6.2 KiB
6.2 KiB
name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, params, output, tested, tests, test_file_path, file_path
| name | kind | lang | domain | version | purity | signature | description | tags | uses_functions | uses_types | returns | returns_optional | error_type | imports | params | output | tested | tests | test_file_path | file_path | |||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| pg_upsert | function | py | infra | 1.0.0 | impure | def pg_upsert(dsn: str, table: str, rows: list[dict], key_cols: list[str], update_cols: list[str] = None) -> dict | UPSERT idempotente en lote en una tabla PostgreSQL con ownership selectivo de columnas. Construye INSERT INTO <table> (cols) VALUES %s ON CONFLICT (key_cols) DO UPDATE SET col = EXCLUDED.col, ... (o DO NOTHING) y lo ejecuta con psycopg2.extras.execute_values. update_cols=None actualiza todas menos key_cols; update_cols=[] hace DO NOTHING; lista explicita = ownership selectivo (las no listadas conservan su valor). Distingue insert vs update via el pseudo-columna xmax (RETURNING (xmax = 0) AS inserted). Valida que table y columnas casen ^[A-Za-z_][A-Za-z0-9_]*$ antes de interpolarlas; los valores van por placeholders. Commit al exito, rollback al fallo, cierre en try/finally. Devuelve {status:'ok', inserted, updated} o {status:'error', error} sin lanzar. Espejo de duckdb_upsert para Postgres. key_cols deben tener PRIMARY KEY o UNIQUE. Depende de psycopg2 (2.9.x en python/.venv). |
|
false | error_py_core |
|
|
dict. En exito: {status:'ok', inserted:int, updated:int} (inserted = filas con xmax=0 en RETURNING, updated = filas en conflicto actualizadas). Con DO NOTHING las filas en conflicto no se devuelven por RETURNING y no cuentan en ninguno. En error (sin lanzar): {status:'error', error:str}. | true |
|
python/functions/infra/pg_upsert_test.py | python/functions/infra/pg_upsert.py |
Ejemplo
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from infra.pg_upsert import pg_upsert
dsn = "postgresql://user:pass@localhost:5433/trends"
# La tabla leads(email PRIMARY KEY, name TEXT, score INT) ya existe.
# Re-ingest 1: inserta el lead.
print(pg_upsert(
dsn, "leads",
[{"email": "ana@x.com", "name": "Ana", "score": 0}],
key_cols=["email"],
))
# {'status': 'ok', 'inserted': 1, 'updated': 0}
# Re-ingest 2: el feed trae name actualizado y score=0 (default del feed),
# pero solo autorizamos actualizar 'name'. 'score' lo posee la DB y NO se pisa.
print(pg_upsert(
dsn, "leads",
[{"email": "ana@x.com", "name": "Ana Lopez", "score": 0}],
key_cols=["email"],
update_cols=["name"],
))
# {'status': 'ok', 'inserted': 0, 'updated': 1}
Cuando usarla
Usala cuando un re-ingest periodico no debe pisar campos que ya posee la DB: pasa
update_cols SIN esos campos (ownership selectivo). Tipico en pipelines de ingesta
idempotente (catalogo, leads, precios competencia, entidades OSINT) donde una fila
se reinserta y ciertas columnas se enriquecieron despues (score calculado, anotacion
manual, flag derivado) y deben sobrevivir al refresco. update_cols=None para un
upsert "todo" clasico, update_cols=[] para insertar solo filas nuevas. Es el espejo
de duckdb_upsert para Postgres. Para append-only puro usa pg_insert_rows.
Gotchas
- Escritura real en disco (impura).
ON CONFLICT (key_cols)solo funciona si esas columnas tienen PRIMARY KEY o UNIQUE en la tabla; sin esa restriccion Postgres lanza error y vuelve como{status:'error', ...}. La tabla debe existir de antemano (la funcion NO la crea — usapg_create_table_from_rows). - Fiabilidad de inserted/updated: el conteo usa el pseudo-columna del sistema
xmax(RETURNING (xmax = 0)). Es la tecnica estandar y fiable en el caso normal (single-writer, sin triggers raros): xmax = 0 = INSERT puro, xmax != 0 = UPDATE por conflicto. Caveats conocidos: (1) conupdate_cols=[](DO NOTHING) las filas en conflicto NO se devuelven por RETURNING, asi que ni cuentan como insert ni como update — solo se reportan las filas nuevas eninserted; (2) si la tabla tiene BEFORE INSERT/UPDATE triggers, REPLICA IDENTITY o subtransacciones que tocan la fila, el valor de xmax puede no ser 0 en un insert real y desviar el conteo. - Inyeccion SQL:
tabley los nombres de columna se validan contra^[A-Za-z_][A-Za-z0-9_]*$antes de interpolarlos (no se pueden parametrizar identificadores). Un nombre con espacios, comillas, puntos o vacio devuelve{status:'error'}. Los valores de las filas siempre van por los placeholders deexecute_values. - Esquema fijo por la primera fila: el conjunto de columnas de insercion lo
determina
rows[0]. Todas las filas deben tener exactamente las mismas claves; si una difiere, se devuelve error (no se hace insercion parcial). - Single-statement por lote: todo el lote va en un solo
INSERT ... VALUES %sdentro de una transaccion. Si una fila viola una constraint (FK, NOT NULL en una columna ausente), Postgres aborta el lote entero y se hace rollback. - Nunca lanza: DSN invalido, tabla sin UNIQUE, tipo invalido o falta de psycopg2
vuelven como
{status:'error', error:str}.