Módulo 1: From File Format To Table Format

Instalando PyIceberg y un catálogo local

Descripción

Esta lección deja la teoría atrás por el resto del módulo. Instala PyIceberg de verdad, en tu propia máquina —sin cuenta de nube, sin JVM, sin Docker—, y crea el primer catálogo local de Kiosko: el índice del álbum del que hablaron las tres lecciones anteriores, ahora como código real que corre en tu terminal.

Conexión con el módulo. Las lecciones 1 a 3 construyeron el porqué: cuatro veces que un Parquet suelto no alcanzó, y la distinción precisa entre formato de archivo y formato de tabla. Esta lección instala la primera pieza concreta —el catálogo— sobre la que las lecciones 5 y 6 van a construir el namespace y la tabla. Sin un catálogo, no hay ningún lugar donde una tabla Iceberg pueda registrarse como "la vigente".

Una analogía: instalar al bibliotecario antes de organizar el primer álbum

Retomando la analogía de las tres lecciones anteriores: si Iceberg es el álbum con índice, y las lecciones 5 y 6 van a llenar ese álbum con la primera colección de fotos, esta lección instala a quien mantiene el índice — el bibliotecario que, desde este momento, va a saber siempre cuál es la versión vigente de cada álbum que exista en esta biblioteca. Antes de tener ni un solo álbum, tiene sentido que exista primero la persona (o, en este caso, el sistema) que va a llevar el registro. Eso es, con precisión, lo que un catálogo de Iceberg es: no contiene ninguna fila de datos por sí mismo — contiene, únicamente, un registro de qué tablas existen y dónde está su metadata vigente.

Ejemplo trabajado: instalación real, catálogo real

Paso 1 — Instala PyIceberg con los extras necesarios

pip install "pyiceberg[sql-sqlite,pyarrow]"

Fíjate en los dos extras entre corchetes, porque ninguno es opcional para lo que esta guía necesita: sql-sqlite instala el soporte para un catálogo respaldado por SQLite —el motor de catálogo que vas a usar durante casi toda esta guía—, y pyarrow instala el motor que PyIceberg usa para leer y escribir los archivos de datos Parquet, y que ya conoces de las guías anteriores del ecosistema.

Qué esperar (verificado corriendo el comando real, en un entorno virtual limpio; la lista completa de dependencias transitivas se omite por espacio — se muestra el resumen final):

Collecting pyiceberg[pyarrow,sql-sqlite]
  ...
Successfully installed annotated-types-0.8.0 cachetools-6.2.6 certifi-2026.7.22
charset_normalizer-3.5.0 click-8.4.2 fsspec-2026.7.0 idna-3.18 markdown-it-py-4.2.0
mdurl-0.1.2 mmh3-5.2.1 pyarrow-25.0.1 pydantic-2.13.4 pydantic-core-2.46.4
pygments-2.20.0 pyiceberg-0.11.1 pyiceberg-core-0.7.0 pyparsing-3.3.2 pyroaring-1.1.0
python-dateutil-2.9.0.post0 requests-2.34.2 rich-14.3.4 six-1.17.0 sqlalchemy-2.0.52
strictyaml-1.7.3 tenacity-9.1.4 typing-extensions-4.16.0 typing-inspection-0.4.4
urllib3-2.7.0 zstandard-0.25.0

La versión que resuelve este comando, verificada al escribir esta lección, es PyIceberg 0.11.1 —publicada el 3 de marzo de 2026, requiere Python >=3.10,<4.0, licencia Apache-2.0—, con pyarrow 25.0.1 y sqlalchemy 2.0.52 como las dos dependencias que hacen posible, respectivamente, leer/escribir Parquet y hablar con el catálogo SQLite. Todo el código y toda la salida del resto de esta guía están verificados contra esta versión exacta.

Confirma la instalación:

python3 -c "import pyiceberg; print('pyiceberg', pyiceberg.__version__)"

Qué esperar (verificado corriendo el comando real):

pyiceberg 0.11.1

Paso 2 — Carga tu primer catálogo local

Con PyIceberg instalado, load_catalog() es la función central de la biblioteca: le dices qué tipo de catálogo quieres, dónde vive su base de datos de registro, y dónde en el filesystem debe escribir los archivos de datos de cualquier tabla nueva.

# create_catalog.py
import os

from pyiceberg.catalog import load_catalog

warehouse_path = os.path.abspath("kiosko_warehouse")
catalog_db_path = os.path.abspath("kiosko_catalog.db")
os.makedirs(warehouse_path, exist_ok=True)

catalog = load_catalog(
    "kiosko",
    type="sql",
    uri=f"sqlite:///{catalog_db_path}",
    warehouse=f"file://{warehouse_path}",
)

print("Catalogo cargado:", catalog.name)
print("Tipo:", type(catalog).__name__)
print("Namespaces existentes:", catalog.list_namespaces())

Qué esperar (verificado corriendo el script real, en un directorio vacío):

Catalogo cargado: kiosko
Tipo: SqlCatalog
Namespaces existentes: []

catalog.list_namespaces() devuelve una lista vacía — tiene sentido, porque este catálogo se acaba de crear, y todavía no le pediste que registrara ningún namespace (eso es, con precisión, el trabajo de la lección 5). Pero fíjate en lo que ya pasó en disco, sin que hayas creado ninguna tabla todavía:

ls -la kiosko_catalog.db kiosko_warehouse/

Qué esperar:

-rw-r--r--  1 usuario  staff  20480 <fecha> kiosko_catalog.db

kiosko_warehouse:
total 0
drwxr-xr-x  2 usuario  staff  64 <fecha> .
drwxr-xr-x  N usuario  staff N*32 <fecha> ..

kiosko_catalog.db ya existe, con 20 KB de tablas internas de SQLite que PyIceberg creó para llevar su propio registro —vas a inspeccionar exactamente qué guarda esa base de datos en el módulo 2—; kiosko_warehouse/ existe como directorio, pero está completamente vacío — todavía no hay ningún archivo de datos ni de metadata dentro, porque no existe ninguna tabla. El catálogo está listo; el álbum sigue sin ninguna página.

Diagrama: qué acaba de instalarse

flowchart LR
    A["pip install\npyiceberg[sql-sqlite,pyarrow]"] --> B["load_catalog('kiosko', type='sql', ...)"]
    B --> C["kiosko_catalog.db\n(SQLite -- registro de namespaces y tablas)"]
    B --> D["kiosko_warehouse/\n(filesystem -- vacio, sin tablas todavia)"]
    C -.->|"leccion 5"| E["catalog.create_namespace('kiosko')"]
    D -.->|"leccion 5-6"| F["catalog.create_table(...)\ntable.append(...)"]

Profundización: por qué un catálogo SQL, y no otra opción

PyIceberg soporta varios tipos de catálogo —sql (el que acabas de usar, respaldado por SQLite, Postgres o MySQL), rest (el protocolo estándar que hablan los catálogos gestionados como AWS Glue Catalog o Polaris, nombrado sin implementar en el módulo 7), hadoop (basado únicamente en el filesystem, sin ninguna base de datos)—. Esta guía elige sql con SQLite específicamente porque cumple, sin fricción, tres condiciones que el resto de la guía necesita: corre completamente local, sin ningún servidor que levantar aparte; no requiere ninguna cuenta ni credencial; y soporta el control de concurrencia optimista —la garantía de que dos escrituras simultáneas a la misma tabla no puedan corromperla— con las mismas garantías transaccionales que cualquier base de datos SQL real, algo que un catálogo puramente basado en archivos (hadoop) no puede ofrecer con la misma solidez. El módulo 7 de esta guía nombra los catálogos de producción —REST, AWS Glue Catalog, Unity Catalog, Polaris— y explica qué garantía resuelve cada uno, sin implementarlos: esta guía completa usa, de principio a fin, catálogos 100% locales.

Errores comunes

Instalar pyiceberg sin los extras, y descubrir el error recién al crear una tabla. Qué pasa: alguien corre pip install pyiceberg a secas, sin [sql-sqlite,pyarrow], y el import pyiceberg funciona sin ningún error — el problema recién aparece en la lección 5, al intentar load_catalog(type="sql", ...), con un error de importación sobre un módulo de SQLite faltante. Por qué pasa: PyIceberg está diseñado de forma modular a propósito —no todo el mundo necesita SQLite ni pyarrow—, así que el paquete base se instala sin fallar, aunque le falten piezas que vas a necesitar más adelante. Cómo detectarlo: si load_catalog(type="sql", ...) falla con un ModuleNotFoundError mencionando sqlalchemy o algo relacionado con SQL, revisa cómo instalaste PyIceberg. Cómo corregirlo: reinstala con los extras exactos de esta lección: pip install "pyiceberg[sql-sqlite,pyarrow]" — las comillas dobles no son decorativas, evitan que la shell interprete los corchetes como un patrón de expansión de archivos.

Usar una ruta relativa para warehouse, y que las tablas "desaparezcan" al cambiar de directorio de trabajo. Qué pasa: alguien escribe warehouse="file://./kiosko_warehouse" (una ruta relativa) en vez de una ruta absoluta, y en una corrida posterior, desde otra carpeta, PyIceberg reporta que la tabla kiosko.fact_orders no existe. Por qué pasa: una ruta relativa se resuelve contra el directorio de trabajo en el momento de correr el script — si corres el script una vez desde ~/kiosko/ y otra vez desde ~/kiosko/scripts/, la ruta relativa apunta a dos lugares físicamente distintos en disco, aunque el catálogo diga lo mismo. Cómo detectarlo: si catalog.list_tables("kiosko") devuelve una lista vacía después de haber creado tablas en una corrida anterior, sospecha primero de una ruta relativa inconsistente antes de asumir que algo se corrompió. Cómo corregirlo: el ejemplo trabajado de esta lección usa os.path.abspath() exactamente para prevenir este error — siempre resuelve warehouse_path y catalog_db_path a rutas absolutas antes de pasarlas a load_catalog(), sin importar desde qué directorio corras el script.

Olvidar crear el directorio del warehouse antes de cargar el catálogo. Qué pasa: alguien corre load_catalog() apuntando a un warehouse_path que todavía no existe como carpeta en disco, y el catálogo se crea sin error —PyIceberg no valida esto por adelantado—, pero la primera escritura real (en la lección 6) falla con un error de sistema de archivos. Cómo detectarlo: si table.append() falla con un error relacionado con una ruta que no existe, revisa si el directorio warehouse_path se creó de verdad antes de cargar el catálogo. Cómo corregirlo: el ejemplo trabajado de esta lección incluye os.makedirs(warehouse_path, exist_ok=True) antes de load_catalog(), exactamente para garantizar que el directorio exista desde el principio — un patrón que vale la pena mantener en cualquier script nuevo del resto de esta guía.

Ejercicios

Ejercicio 1 — Reproduce la instalación completa tú mismo. En tu propia máquina, con Python >=3.10 disponible, corre pip install "pyiceberg[sql-sqlite,pyarrow]", confirma la versión con python3 -c "import pyiceberg; print(pyiceberg.__version__)", y después corre el script create_catalog.py de esta lección. Confirma que ves Namespaces existentes: [] y que kiosko_warehouse/ aparece como directorio vacío.

Ver solución

Si tu versión de Python cumple >=3.10,<4.0, deberías ver una instalación exitosa con pyiceberg-0.11.1 (o una versión más nueva si instalas esto más adelante en el tiempo — PyIceberg publica versiones con regularidad) en la línea Successfully installed, y el script debería imprimir exactamente Catalogo cargado: kiosko, Tipo: SqlCatalog, Namespaces existentes: []. Si el import pyiceberg falla, confirma primero tu versión de Python con python3 --version — es la causa más común de un fallo en este paso.

Ejercicio 2 — Predicción: ¿qué contiene kiosko_catalog.db en este punto? Sin abrir el archivo todavía —el módulo 2 lo hace a fondo—, predice: si inspeccionaras kiosko_catalog.db con cualquier cliente de SQLite después de correr solo el create_catalog.py de esta lección (sin haber creado ningún namespace ni tabla todavía), ¿esperas encontrar tablas con datos de Kiosko adentro? Justifica tu respuesta con lo que aprendiste sobre qué es un catálogo.

Ver solución

No — kiosko_catalog.db en este punto contiene únicamente las tablas internas que PyIceberg necesita para funcionar como catálogo (el esquema propio de SqlCatalog, pensado para registrar namespaces y punteros a tablas), pero ninguna fila con datos de negocio de Kiosko todavía, porque el ejemplo trabajado de esta lección se detuvo justo antes de crear el namespace kiosko (catalog.list_namespaces() devolvió una lista vacía). El catálogo, en este punto, es exactamente como un índice recién impreso para un álbum que todavía no existe: la estructura está lista, pero no hay ninguna entrada real que apunte a algo.

Ejercicio 3 — Explica la diferencia entre catalog_db_path y warehouse_path. En 2-3 frases, explica qué guarda cada una de las dos rutas del ejemplo trabajado de esta lección, y por qué son dos cosas distintas en vez de una sola.

Ver solución

catalog_db_path es la ubicación de la base de datos SQLite que actúa como el catálogo: el registro de qué namespaces y tablas existen, y hacia qué archivo de metadata apunta cada tabla en este momento — es, en la analogía de esta lección, el bibliotecario y su fichero. warehouse_path es la ubicación en el filesystem donde viven los archivos reales de cada tabla —tanto los archivos de datos Parquet como los archivos de metadata JSON/Avro que el módulo 2 va a inspeccionar—; es, en la misma analogía, el estante físico donde viven los álbumes en sí. Son dos cosas distintas porque cumplen roles distintos: el catálogo sabe qué existe y dónde encontrarlo; el warehouse contiene lo que existe. Nada impide, en un despliegue real, que uno viva en una base de datos gestionada en la nube y el otro en un almacenamiento de objetos como S3 — esta guía los mantiene ambos locales, por simplicidad y costo cero.

Resumen y siguiente paso

En esta lección instalaste PyIceberg de verdad —versión 0.11.1 al momento de escribir esta guía, con los extras sql-sqlite y pyarrow— y creaste tu primer catálogo local, kiosko, respaldado por SQLite, con un warehouse vacío en el filesystem. Confirmaste, con catalog.list_namespaces() devolviendo una lista vacía, que el catálogo existe pero todavía no tiene ningún namespace registrado.

Antes de avanzar deberías poder: explicar la diferencia entre catalog_db_path y warehouse_path; reproducir la instalación y la carga del catálogo en tu propia máquina; y nombrar por qué esta guía eligió un catálogo sql/SQLite en vez de hadoop o rest.

El catálogo existe, pero está vacío. La lección 5 crea el primer namespacekiosko— y la primera tablakiosko.fact_orders—, con un esquema explícito, dentro de ese catálogo.

Recursos

  • PyIceberg — documentación oficial (quickstart), instalación exacta con extras y configuración de un SqlCatalog local con SQLite. py.iceberg.apache.org. En inglés.
  • PyIceberg — PyPI, versión vigente 0.11.1, publicada el 3 de marzo de 2026, requisitos de Python y licencia Apache-2.0. pypi.org/project/pyiceberg. En inglés.
  • PyIceberg — referencia de API, load_catalog() y los tipos de catálogo soportados (sql, rest, hadoop, glue). py.iceberg.apache.org/api. En inglés.
  • DISEÑO de esta guía — la sección "Qué SÍ se ejecuta / verifica", fuente de la configuración exacta del SqlCatalog que usa esta lección. src/guides/lakehouse-and-iceberg-guide/DISENO.md. En español.