1c4a4b9259
Cinco funciones nuevas para soportar DuckDB como fuente de verdad del project osint:
Grupo duckdb (escritura, complementan a duckdb_query_readonly):
- duckdb_execute_py_infra (impure): ejecuta INSERT/UPDATE/DELETE/DDL en read-write, commit, {status,rowcount}. 6 tests.
- duckdb_upsert_py_infra (impure): UPSERT ON CONFLICT actualizando solo update_cols → ownership selectivo (un re-upsert no pisa columnas excluidas). 7 tests.
Grupo dav (libretas de contactos + vCard multi-valor):
- dav_make_addressbook_py_infra (impure): crea una libreta CardDAV nueva via extended MKCOL (RFC 5689). Idempotente. 12 tests.
- dav_list_addressbooks_py_infra (impure): lista las libretas del contacts-home (PROPFIND Depth:1). 7 tests.
- build_vcard_py_core (pure): serializa un contacto a vCard 3.0 multi-valor (N TEL/EMAIL/ADR + X-OSINT-*). 5 tests.
Paginas de capacidad duckdb.md y dav.md actualizadas.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.2 KiB
4.2 KiB
Capability: duckdb
Operar bases de datos DuckDB desde el registry: abrir/crear bases, consultas read-only seguras, conversion CSV -> Parquet, deduplicacion por hash y carga de series temporales. DuckDB es el motor analitico embebido del ecosistema (OLAP local, archivos .duckdb, lectura directa de CSV/Parquet/JSON).
Pieza central del patron BD como fuente de verdad + Obsidian como vista (project osint): la app osint_db posee la DuckDB maestra y este grupo aporta las primitivas de acceso.
Funciones
| ID | Firma | Que hace |
|---|---|---|
duckdb_open_go_infra |
DuckDBOpen(path string) (*sql.DB, error) |
Abre (o crea) una base DuckDB desde Go. Path vacio o :memory: abre en memoria. |
duckdb_query_readonly_py_infra |
duckdb_query_readonly(db_path, sql, params=None, max_rows=10000) -> dict |
Consulta read-only segura: conexion read_only=True, params posicionales ?, filas como list[dict] con tipos normalizados a JSON (date/datetime -> isoformat, Decimal -> float, bytes -> base64). Devuelve {status, columns, rows, row_count, truncated} sin lanzar. |
duckdb_execute_py_infra |
duckdb_execute(db_path, sql, params=None) -> dict |
Ejecuta UNA sentencia de escritura (INSERT/UPDATE/DELETE/DDL) en conexion read-write, commit, devuelve {status, rowcount} sin lanzar. Primitivo de escritura del grupo (complementa a duckdb_query_readonly). |
duckdb_upsert_py_infra |
duckdb_upsert(db_path, table, rows, key_cols, update_cols=None) -> dict |
UPSERT idempotente INSERT ... ON CONFLICT (key_cols) DO UPDATE SET ... actualizando SOLO update_cols. Excluir columnas de update_cols permite que un re-upsert NO las pise (ownership selectivo: la DB es la verdad). Devuelve {status, inserted, updated}. |
csv_to_parquet_duckdb_py_core |
csv_to_parquet_duckdb(csv_path, parquet_path, column_casts=None, overwrite=False) -> bool |
Convierte CSV -> Parquet con read_csv_auto. column_casts fuerza tipos por columna. No reescribe si el parquet existe y overwrite=False. |
dedup_duckdb_table_by_hash_py_pipelines |
dedup_duckdb_table_by_hash(duckdb_path, table, exclude_cols=None) -> dict |
Pipeline: anade columna row_hash (md5 de columnas de datos) idempotentemente y borra filas duplicadas conservando la primera insercion. |
load_ohlcv_from_duckdb_go_finance |
LoadOHLCVFromDuckDB(dbPath, query string) ([][]float64, error) |
Carga datos OHLCV ejecutando una query SQL sobre una base DuckDB (consumo desde apps Go de finanzas). |
Ejemplo canonico
Consulta read-only desde cualquier sesion (la conexion se abre read_only=True y se cierra siempre):
cd /home/enmanuel/fn_registry
python/.venv/bin/python3 - <<'PYEOF'
import sys
sys.path.insert(0, "python/functions")
from infra import duckdb_query_readonly
res = duckdb_query_readonly(
"projects/osint/apps/osint_db/data/osint.duckdb",
"SELECT contexto, COUNT(*) AS n FROM persons GROUP BY contexto ORDER BY n DESC",
max_rows=50,
)
print(res["status"], res["row_count"])
for row in res["rows"]:
print(row)
PYEOF
Conversion CSV -> Parquet en una linea:
./fn run csv_to_parquet_duckdb datos.csv datos.parquet
Gotchas del grupo
- Single-writer: DuckDB permite UN solo proceso escritor por archivo. Si un service (ej.
osint_db) posee la base, el resto de procesos deben leer conread_only=True(duckdb_query_readonlyya lo hace) o pasar por la API HTTP del service. Las funciones de escritura (duckdb_execute,duckdb_upsert) abren en read-write y SOLO debe usarlas el proceso dueño de la base (dentro de su write lock), nunca un cliente concurrente. - Version del motor: el formato de archivo puede cambiar entre versiones mayores de DuckDB. El venv del registry lleva
duckdb1.5.x; no mezclar con CLIs/WASM antiguos sobre el mismo archivo. read_only=Trueexige que el archivo exista — no crea bases nuevas.
Fronteras
- NO cubre SQLite (
sqlite_open_go_infray el grupo de operations.db van aparte). - NO cubre el render de resultados a Markdown/notas — eso es
render_markdown_table_py_core+upsert_sentinel_block_py_core(grupoobsidian). - El analisis exploratorio pesado (notebooks) vive en
analysis/con sus propios venvs.