Vibe Coding #3 - Creando una librería Python para una API | Datos energéticos europeos ENTSO-E
Construyendo una librería Python para la API de la Plataforma de Transparencia ENTSO-E. Desde un wrapper básico hasta un paquete PyPI publicable con interfaz limpia, type hints y documentación.
Recibe actualizaciones semanales
Sesiones de live coding, librerías Python para datos energéticos y tutoriales prácticos. Un email por semana, sin relleno.
Resultado
Una librería Python limpia y bien tipada que convierte la compleja API XML de ENTSO-E en DataFrames de pandas — lista para análisis y publicada en PyPI.
from entsoe import Client
client = Client(api_key="tu-token")
prices = client.prices.day_ahead(area="Spain", start="2026-02-01", end="2026-02-07")
# → DataFrame con precios horarios en EUR/MWh El problema
- La API REST de ENTSO-E devuelve XML complejo que requiere parseo manual
- Las librerías Python existentes usan nombres de función como parámetros, lo que dificulta el autocompletado
- Me llevó varios intentos entender qué códigos de área y parámetros necesitaba para cada consulta
- En Vibe Coding #2 construimos herramientas MCP — ahora empaquetamos el cliente API subyacente como librería reutilizable

La solución
Partimos del cliente API básico usado en el Vibe Coding #2 para las herramientas MCP. El objetivo: transformarlo en una librería publicable con una interfaz limpia e intuitiva.
Diseño de la API
Empezamos analizando la librería eia-py (cliente para la API de energía de EE.UU.) como referencia de diseño:

Lo que nos gusta: autocompletado, agrupación lógica de métodos, sin strings mágicos. El objetivo: llevar esa misma experiencia a ENTSO-E.

Claude Code propuso tres opciones de diseño:
- Opción A: Funciones planas (
get_day_ahead_prices(),get_generation_mix(), etc.) - Opción B: Clases por tipo de dato (
Prices,Generation,Transmission) - Opción C: Namespaces tipados con accesores (
client.prices.day_ahead(),client.generation.mix())

Seleccionamos la Opción C — namespaces con type hints completos para aprovechar el autocompletado del IDE y descubrimiento de métodos mediante IntelliSense.
Construyendo la librería
Claude Code generó toda la estructura de la librería en una sola pasada:

Primeros resultados
Ejecutamos la primera consulta real — mix de generación de Francia:
client = Client(api_key=os.getenv("ENTSOE_API_KEY"))
generation = client.generation.mix(area="France", start="2026-02-01", end="2026-02-07") 
El resultado: un DataFrame limpio con tipos de generación en columnas y timestamps como índice — listo para análisis.

Arquitectura de namespaces
La librería organiza métodos en cinco namespaces principales:
client.load # Demanda eléctrica (actual + forecast)
client.prices # Precios day-ahead, intraday, clearing
client.generation # Mix, forecast, unidades instaladas
client.transmission # Flujos transfronterizos, capacidad
client.balancing # Precios de equilibrio, reservas Cada namespace mapea a una clase dedicada que encapsula la lógica de consulta y transformación.
Manejo de códigos de área
La API de ENTSO-E usa códigos de área EIC (Energy Identification Code) — por ejemplo, 10YFR-RTE------C para Francia. Para evitar que los usuarios tengan que memorizarlos, implementamos un sistema de resolución:
# Soporta nombres, códigos ISO y códigos EIC
client.prices.day_ahead(area="Spain") # → 10YES-REE------0
client.prices.day_ahead(area="ES") # → 10YES-REE------0
client.prices.day_ahead(area="10YES-REE------0") # → pass-through 
Más de 60 áreas soportadas — desde países completos hasta zonas de oferta regionales.
Parsing XML → DataFrame
La API devuelve documentos XML en formato GL_MarketDocument. Implementamos parsers especializados para cada tipo de consulta.
Cada parser extrae series temporales del XML y las convierte en DataFrames de pandas con columnas tipadas y timestamps como índice.
División automática de consultas
La API de ENTSO-E tiene un límite de un año por consulta. Para rangos más largos, la librería divide automáticamente en consultas anuales y concatena los resultados:
# Consulta de 3 años → automáticamente dividida en 3 peticiones
prices = client.prices.day_ahead(
area="Germany",
start="2023-01-01",
end="2026-01-01"
)
# → DataFrame con 3 años de datos Lógica de reintentos y rate limiting
Implementamos reintentos exponenciales con jitter para manejar errores de red y límites de tasa:
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=4, max=10) + wait_random(0, 2),
retry=retry_if_exception_type((httpx.HTTPStatusError, httpx.RequestError))
)
def _make_request(self, params: dict) -> bytes:
response = self.session.get(self.base_url, params=params)
response.raise_for_status()
return response.content Manejo de archivos ZIP
Algunos endpoints devuelven respuestas comprimidas en ZIP. La librería detecta y descomprime automáticamente:
def _extract_xml(self, content: bytes) -> str:
# Detecta si la respuesta es ZIP
if content.startswith(b'PK'):
with zipfile.ZipFile(io.BytesIO(content)) as zf:
xml_file = zf.namelist()[0]
return zf.read(xml_file).decode('utf-8')
return content.decode('utf-8') Publicación en PyPI
Configuramos el proyecto para publicación usando uv:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "python-entsoe"
version = "0.1.0"
description = "Typed Python client for ENTSO-E Transparency Platform API"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"httpx>=0.27.0",
"pandas>=2.2.0",
"tenacity>=9.0.0",
] Subimos el paquete en directo:
uv build
uv publish Publicado en menos de 20 segundos:

La página del paquete en PyPI muestra el README con instrucciones de instalación, ejemplos rápidos y la referencia completa de namespaces:

Para validar que todo funciona end-to-end, instalamos desde un proyecto completamente nuevo — pip install python-entsoe, import, consulta, gráfico. Todo funcionó al primer intento:

CI/CD con GitHub Actions
Configuramos GitHub Actions para automatizar tests y publicación:

El workflow ejecuta tests en cada push y publica automáticamente en PyPI cuando se crea un tag de versión.
Referencia
Namespaces y métodos principales
| Namespace | Método | Parámetros | Descripción |
|---|---|---|---|
| client.load | actual() | area, start, end | Demanda eléctrica real (MW) |
forecast() | area, start, end | Previsión de demanda (MW) | |
| client.prices | day_ahead() | area, start, end | Precios day-ahead (EUR/MWh) |
intraday() | area, start, end | Precios intraday (EUR/MWh) | |
| client.generation | mix() | area, start, end | Mix por tipo de fuente (MW) |
forecast() | area, start, end | Previsión de generación (MW) | |
installed() | area, start, end | Capacidad instalada (MW) | |
| client.transmission | flows() | from_area, to_area, start, end | Flujos transfronterizos (MW) |
capacity() | from_area, to_area, start, end | Capacidad de interconexión (MW) | |
| client.balancing | prices() | area, start, end | Precios de equilibrio (EUR/MWh) |
Estructura del proyecto
Stack
- Python 3.11+ / httpx / pandas / tenacity
- uv (gestión de dependencias + build + publicación)
- GitHub Actions (CI/CD)
- PyPI (distribución)
Recursos
- python-entsoe en PyPI - Paquete publicado listo para instalar
- Repositorio GitHub - Código fuente y documentación
- ENTSO-E Transparency Platform - Fuente oficial de datos
- ENTSO-E API Documentation - Especificación de la API REST
- eia-py - Inspiración para el diseño de la API