---
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.
