Files
fn_registry/python/functions/infra/load_folder_to_duckdb.md
T
egutierrez 6a1520f458 feat(eda): EDA de carpeta/base multi-tabla -> AutomaticEDA por capitulos (PDF+PPTX+MD)
Pipeline render_automatic_eda_folder: apunta el AutomaticEDA a una CARPETA de
archivos tabulares (CSV/Parquet/JSON) o a una DuckDB existente y emite el informe
de la BASE por capitulos en PDF (A5 movil) + PPTX (16:9) + Markdown. Documento-base
con portada-base, resumen de todas las tablas y relaciones inter-tabla (FK
candidatas por containment + diagrama Mermaid del join graph). Flag per_table_eda
anexa el mini-EDA de cada tabla. Aditivo: render_automatic_eda (tabla unica) intacto.

Funcion nueva load_folder_to_duckdb (infra, grupo eda+duckdb): carga una carpeta a
una DuckDB (temp si no se da path), CREATE TABLE por archivo con read_csv_auto/
read_parquet/read_json_auto. dict-no-throw.

Compone profile_database + los 3 renderers del motor AutomaticEDA + build_document
(per-tabla), sin reimplementar su logica. Tests: golden 3 CSV relacionados (FK
orders.customer_id->customers.id detectada) + edges (carpeta vacia, 1 tabla,
DuckDB existente, path inexistente). fn index sin error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:34:10 +02:00

6.3 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
load_folder_to_duckdb function py infra 1.0.0 impure def load_folder_to_duckdb(folder: str, db_path: str = None, pattern: str = '*.csv,*.parquet,*.json') -> dict Escanea el primer nivel de una CARPETA buscando archivos tabulares (CSV/TSV/TXT, Parquet, JSON/NDJSON) y los carga como tablas en una base DuckDB usando los lectores nativos read_csv_auto/read_parquet/read_json_auto. Es la pieza de entrada del EDA a nivel de carpeta (grupo eda). Por cada archivo crea una tabla cuyo nombre se deriva del basename saneado a [0-9a-zA-Z_] en minusculas (prefijo t_ si empieza por digito, sufijos _2/_3 ante colisiones, tabla_<i> si queda vacio). El path se escapa (comilla simple '->'') antes de interpolarlo porque los lectores DuckDB no aceptan el path como parametro posicional. Glob NO recursivo: un glob.glob(os.path.join(folder, g)) por cada patron del CSV, dedup y ordenado. db_path=None genera una DuckDB temporal (mkstemp, se borra el placeholder vacio porque DuckDB rechaza un archivo de 0 bytes) y devuelve su ruta. Un fallo al cargar un archivo concreto no aborta el resto: se registra en errors y se continua. Devuelve siempre un dict sin lanzar (estilo del grupo duckdb): {status:'ok', db_path, tables, errors} en exito (carpeta sin archivos tabulares incluida, tables=[]) y {status:'error', error} cuando la carpeta no existe o falla algo global. Depende del paquete duckdb (1.5.2).
eda
duckdb
ingest
etl
folder
false error_py_core
glob
os
re
tempfile
duckdb
name desc
folder ruta a un directorio. Se escanea solo su primer nivel (NO recursivo). Si no existe o no es un directorio devuelve {status:'error'} sin lanzar.
name desc
db_path ruta del archivo DuckDB destino, abierto en modo read-write (lo crea si no existe). None (default) genera una DuckDB temporal unica con tempfile.mkstemp y devuelve su ruta en el campo db_path del retorno. DuckDB es single-writer: si otro proceso lo tiene abierto en escritura, connect falla con error de lock devuelto en el dict.
name desc
pattern CSV de globs separados por coma (default '*.csv,*.parquet,*.json'). Cada glob se aplica con glob.glob(os.path.join(folder, g)) sobre el primer nivel de folder; los resultados de todos los globs se deduplican y ordenan. Los globs con ** NO descienden recursivamente (glob.glob sin recursive=True).
dict. En exito: {status:'ok', db_path:str (ruta DuckDB usada), tables:[{name:str, source_file:str, n_rows:int}], errors:[{name?:str, source_file:str, error:str}]}. La carpeta sin archivos tabulares es un exito con tables=[] y errors=[]. En error (sin lanzar): {status:'error', error:str}. true
test_carga_dos_csv_como_tablas
test_db_path_none_crea_temporal
test_carpeta_vacia_es_ok_sin_tablas
test_carpeta_inexistente_devuelve_status_error
python/functions/infra/load_folder_to_duckdb_test.py python/functions/infra/load_folder_to_duckdb.py

Ejemplo

import sys
sys.path.insert(0, "python/functions")
from infra.load_folder_to_duckdb import load_folder_to_duckdb

# Preparar una carpeta de demo con dos CSV.
import os
os.makedirs("/tmp/eda_folder_demo", exist_ok=True)
with open("/tmp/eda_folder_demo/ventas.csv", "w") as f:
    f.write("id,total\n1,10.5\n2,20.0\n3,5.25\n")
with open("/tmp/eda_folder_demo/clientes.csv", "w") as f:
    f.write("id,nombre\n1,ana\n2,luis\n")

# Cargar todos los tabulares de la carpeta a una DuckDB temporal.
res = load_folder_to_duckdb("/tmp/eda_folder_demo")
print(res["status"])    # ok
print(res["db_path"])   # /tmp/tmpXXXXXXXX.duckdb (temporal)
for t in res["tables"]:
    print(t["name"], t["n_rows"])   # ventas 3  /  clientes 2

# Persistir en una DuckDB concreta y limitar a CSV.
res2 = load_folder_to_duckdb(
    "/tmp/eda_folder_demo",
    db_path="/tmp/eda_folder_demo/folder.duckdb",
    pattern="*.csv",
)
print(res2["tables"])   # [{'name': 'clientes', ...}, {'name': 'ventas', ...}]

Cuando usarla

Cuando tienes una carpeta de datos sueltos (un dump, un export, varios CSV/Parquet descargados) y quieres analizarlos juntos con SQL sin montar la ingesta a mano, archivo por archivo. Es el primer eslabon del EDA a nivel de carpeta (grupo eda): deja una DuckDB con una tabla por archivo, lista para perfilar con duckdb_table_schema_py_infra, consultar con duckdb_query_readonly_py_infra, o correlacionar aguas abajo. Usala antes de cualquier paso de perfilado cuando la unidad de trabajo es "todos los archivos de este directorio".

Gotchas

  • Glob NO recursivo: solo se escanea el primer nivel de folder. Archivos en subdirectorios se ignoran (ni siquiera con ** en el patron, porque glob.glob se llama sin recursive=True). Si necesitas recursion, aplana la carpeta antes o amplia la funcion.
  • Saneo de nombres de tabla: el basename se reduce a [0-9a-zA-Z_] en minusculas. Ventas 2024.csv -> tabla ventas_2024. Dos archivos distintos pueden sanear al mismo nombre (a-b.csv y a_b.csv); el segundo se desambigua con sufijo _2, _3, ... El mapeo real archivo->tabla esta en tables[].name / tables[].source_file, no lo asumas.
  • read_json_auto requiere JSON tabular (array de objetos u objetos NDJSON homogeneos). Un JSON anidado o irregular puede fallar la carga de ESA tabla; el error se registra en errors y el resto de archivos siguen cargandose.
  • Extension desconocida = se salta, no falla: queda anotada en errors con unsupported extension. Mapeo de lectores: .csv/.tsv/.txt->read_csv_auto, .parquet/.pq->read_parquet, .json/.ndjson->read_json_auto.
  • Escritura real en disco (impura). DuckDB es single-writer: si otro proceso tiene db_path abierto en escritura, connect falla con error de lock devuelto en el dict. Un db_path con un directorio padre inexistente tambien falla.
  • db_path=None crea un archivo temporal que NO se borra solo: la ruta se devuelve en db_path para que el llamador la consuma y la limpie cuando termine.
  • Tipos inferidos por los lectores _auto: los tipos de columna los infiere DuckDB. Revisa el schema con duckdb_table_schema_py_infra si el tipado importa aguas abajo.