--- name: clickhouse_insert_rows kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "def clickhouse_insert_rows(base_url: str, table: str, rows: list[dict], *, user: str = 'default', password: str = '', database: str = 'analytics', timeout: float = 30.0) -> int" description: "Inserta una lista de dicts en ClickHouse via HTTP (puerto 8123) usando el formato JSONEachRow. Retorna el numero de filas enviadas." tags: [clickhouse, analytics, http, insert, ingest, etl] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [json, urllib.request, urllib.parse, urllib.error] tested: false tests: [] test_file_path: "" file_path: "python/functions/infra/clickhouse_insert_rows.py" params: - name: base_url desc: "URL base del servidor ClickHouse sin trailing slash. Ej: 'http://127.0.0.1:18123'. Para tunel SSH, apunta al puerto local reenviado." - name: table desc: "Nombre de la tabla destino, con o sin prefijo de base de datos. Ej: 'analytics.gnula_movies' o 'gnula_movies'." - name: rows desc: "Lista de dicts a insertar. Cada dict se serializa como una linea JSON. Las claves deben coincidir con columnas existentes; columnas ausentes usan DEFAULT." - name: user desc: "Usuario ClickHouse para autenticacion via header X-ClickHouse-User (default: 'default')." - name: password desc: "Contrasena ClickHouse para autenticacion via header X-ClickHouse-Key (default: cadena vacia)." - name: database desc: "Base de datos ClickHouse enviada como parametro de query (default: 'analytics')." - name: timeout desc: "Timeout de socket en segundos (default: 30.0)." output: "Entero con el numero de filas insertadas (len(rows)). Retorna 0 si rows esta vacio sin contactar el servidor." --- ## Ejemplo ```python from infra import clickhouse_insert_rows n = clickhouse_insert_rows( "http://127.0.0.1:18123", "analytics.gnula_movies", [ { "snapshot_ts": "2026-05-30 14:00:00", "href": "/pelicula/avatar-el-camino-del-agua", "title": "Avatar: El camino del agua", "year": 2022, "flags": "es.png", "lang_es": 1, "status": "pending", "in_library": 0, "detected_at": "2026-05-30T14:00:00", "downloaded_at": "", } ], user="analytics", password="secret", database="analytics", ) print(f"Inserted {n} rows") ``` ## Cuando usarla Cuando un ETL empuja snapshots o eventos a ClickHouse via HTTP (puerto 8123), incluyendo a traves de un tunel SSH a un ClickHouse interno no expuesto publicamente. Alternativa ligera (solo stdlib) a `clickhouse-driver` o `clickhouse-connect` cuando no se quieren dependencias externas. ## Gotchas - `base_url` sin trailing slash: `"http://127.0.0.1:18123"`, no `"http://127.0.0.1:18123/"`. - Fechas y datetimes deben pasarse como strings en formato que ClickHouse acepte (`"YYYY-MM-DD HH:MM:SS"`) o como enteros epoch. El caller formatea; esta funcion no convierte tipos. - Arrays van como listas JSON nativas Python: `{"tags": ["drama", "sci-fi"]}`. - Columnas ausentes en un dict usan el valor DEFAULT de la tabla (JSONEachRow ignora claves faltantes). No falla. - Para tunel SSH: `ssh -L 18123:localhost:8123 user@host` y usar `base_url="http://127.0.0.1:18123"`. - En caso de error HTTP, `ValueError` incluye el codigo y los primeros 500 caracteres del cuerpo — util para depurar errores de schema o SQL malformado. - Lotes grandes: no hay batching interno. Si `rows` tiene miles de elementos, el body puede ser grande. Partir en chunks desde el caller si es necesario.