be5a7b582e
Añade módulo Python con funciones para la API de Metabase en dominio infra. Incluye cliente HTTP, auth, y CRUD de cards, dashboards y users. Proyecto gestionado con uv (pyproject.toml).
228 lines
7.3 KiB
Python
228 lines
7.3 KiB
Python
"""CRUD de cards/preguntas de Metabase y ejecucion de queries."""
|
|
|
|
from .client import MetabaseClient
|
|
|
|
|
|
def metabase_list_cards(
|
|
client: MetabaseClient,
|
|
filter: str = "",
|
|
model_id: int = 0,
|
|
) -> list[dict]:
|
|
"""Lista preguntas/cards de Metabase con filtro opcional.
|
|
|
|
Endpoint: GET /api/card. No tiene paginacion offset/limit.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
filter: "all", "mine", "fav", "archived", "recent", "popular",
|
|
"database", "table". Vacio = todas.
|
|
model_id: ID de database/tabla. Solo aplica con filter "database" o "table".
|
|
|
|
Returns:
|
|
Lista de dicts, cada uno con: id, name, description, display,
|
|
collection_id, database_id, creator_id, archived, dataset_query.
|
|
|
|
Example:
|
|
>>> cards = metabase_list_cards(client, filter="mine")
|
|
>>> for c in cards:
|
|
... print(c["id"], c["name"], c["display"])
|
|
"""
|
|
params = {}
|
|
if filter:
|
|
params["f"] = filter
|
|
if model_id > 0:
|
|
params["model_id"] = model_id
|
|
return client.request("GET", "/api/card", params=params)
|
|
|
|
|
|
def metabase_get_card(client: MetabaseClient, card_id: int) -> dict:
|
|
"""Obtiene los detalles completos de una card/pregunta.
|
|
|
|
Endpoint: GET /api/card/:id.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
card_id: ID de la card.
|
|
|
|
Returns:
|
|
Dict con: id, name, description, display, dataset_query,
|
|
visualization_settings, collection_id, database_id, archived,
|
|
creator, created_at, updated_at.
|
|
|
|
Example:
|
|
>>> card = metabase_get_card(client, 42)
|
|
>>> print(card["name"], card["display"])
|
|
>>> print(card["dataset_query"]["native"]["query"]) # SQL
|
|
"""
|
|
return client.request("GET", f"/api/card/{card_id}")
|
|
|
|
|
|
def metabase_create_card(
|
|
client: MetabaseClient,
|
|
name: str,
|
|
dataset_query: dict,
|
|
display: str = "table",
|
|
collection_id: int = 0,
|
|
description: str = "",
|
|
) -> dict:
|
|
"""Crea una nueva card/pregunta en Metabase.
|
|
|
|
Endpoint: POST /api/card.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
name: Nombre de la pregunta.
|
|
dataset_query: Query de la card. Estructura:
|
|
SQL nativo: {"database": 1, "type": "native", "native": {"query": "SELECT ..."}}
|
|
MBQL: {"database": 1, "type": "query", "query": {"source-table": 4, ...}}
|
|
display: Tipo de visualizacion: "table", "bar", "line", "pie", "scalar",
|
|
"area", "row", "combo", "funnel", "scatter", "waterfall", etc.
|
|
collection_id: ID de coleccion destino. 0 = root.
|
|
description: Descripcion opcional.
|
|
|
|
Returns:
|
|
Dict con la card creada.
|
|
|
|
Example:
|
|
>>> card = metabase_create_card(client, "Revenue by Month", {
|
|
... "database": 1,
|
|
... "type": "native",
|
|
... "native": {"query": "SELECT date_trunc('month', created_at), SUM(total) FROM orders GROUP BY 1"},
|
|
... }, display="line", description="Monthly revenue trend")
|
|
"""
|
|
body: dict = {
|
|
"name": name,
|
|
"dataset_query": dataset_query,
|
|
"display": display,
|
|
"visualization_settings": {},
|
|
}
|
|
if collection_id > 0:
|
|
body["collection_id"] = collection_id
|
|
if description:
|
|
body["description"] = description
|
|
return client.request("POST", "/api/card", json=body)
|
|
|
|
|
|
def metabase_update_card(client: MetabaseClient, card_id: int, **fields) -> dict:
|
|
"""Actualiza campos de una card/pregunta en Metabase.
|
|
|
|
Endpoint: PUT /api/card/:id. Solo se modifican los campos pasados.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
card_id: ID de la card.
|
|
**fields: Campos a actualizar. Validos:
|
|
name (str), description (str), display (str),
|
|
dataset_query (dict), visualization_settings (dict),
|
|
collection_id (int), archived (bool),
|
|
enable_embedding (bool), embedding_params (dict).
|
|
|
|
Returns:
|
|
Dict con la card actualizada.
|
|
|
|
Example:
|
|
>>> metabase_update_card(client, 42, name="Updated Name", archived=True)
|
|
>>> metabase_update_card(client, 42, dataset_query={
|
|
... "database": 1, "type": "native",
|
|
... "native": {"query": "SELECT * FROM users LIMIT 100"},
|
|
... })
|
|
"""
|
|
return client.request("PUT", f"/api/card/{card_id}", json=fields)
|
|
|
|
|
|
def metabase_delete_card(client: MetabaseClient, card_id: int) -> None:
|
|
"""Elimina permanentemente una card/pregunta.
|
|
|
|
Endpoint: DELETE /api/card/:id. IRREVERSIBLE.
|
|
Para soft-delete preferir: metabase_update_card(client, card_id, archived=True)
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
card_id: ID de la card a eliminar.
|
|
|
|
Example:
|
|
>>> metabase_delete_card(client, 42)
|
|
>>> # Preferir soft-delete: metabase_update_card(client, 42, archived=True)
|
|
"""
|
|
client.request("DELETE", f"/api/card/{card_id}")
|
|
|
|
|
|
def metabase_execute_card(
|
|
client: MetabaseClient,
|
|
card_id: int,
|
|
parameters: list[dict] | None = None,
|
|
) -> dict:
|
|
"""Ejecuta la query de una card/pregunta guardada.
|
|
|
|
Endpoint: POST /api/card/:id/query.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
card_id: ID de la card a ejecutar.
|
|
parameters: Parametros para queries parametrizadas. Cada parametro:
|
|
{"type": "category", "target": ["variable", ["template-tag", "tag"]], "value": "val"}
|
|
|
|
Returns:
|
|
Dict con resultados:
|
|
- status: "completed" o "failed"
|
|
- row_count: numero de filas
|
|
- running_time: tiempo en ms
|
|
- data.columns: nombres de columnas
|
|
- data.rows: filas de datos (lista de listas)
|
|
- data.cols: metadata de columnas
|
|
- data.native_form.query: SQL ejecutado
|
|
|
|
Example:
|
|
>>> result = metabase_execute_card(client, 42)
|
|
>>> for row in result["data"]["rows"]:
|
|
... print(row)
|
|
>>> # Con parametros:
|
|
>>> result = metabase_execute_card(client, 42, parameters=[
|
|
... {"type": "category", "target": ["variable", ["template-tag", "status"]], "value": "active"},
|
|
... ])
|
|
"""
|
|
body = {}
|
|
if parameters:
|
|
body["parameters"] = parameters
|
|
return client.request("POST", f"/api/card/{card_id}/query", json=body or None)
|
|
|
|
|
|
def metabase_execute_query(
|
|
client: MetabaseClient,
|
|
database_id: int,
|
|
sql: str,
|
|
max_results: int = 0,
|
|
) -> dict:
|
|
"""Ejecuta una query SQL ad-hoc sin guardarla como card.
|
|
|
|
Endpoint: POST /api/dataset. Util para exploracion rapida y consultas
|
|
que no necesitan persistirse.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
database_id: ID de la database en Metabase.
|
|
sql: Query SQL a ejecutar.
|
|
max_results: Limite de filas. 0 = default 2000.
|
|
|
|
Returns:
|
|
Dict con misma estructura que metabase_execute_card:
|
|
data.columns, data.rows, row_count, running_time, status.
|
|
|
|
Example:
|
|
>>> result = metabase_execute_query(client, 1, "SELECT * FROM users LIMIT 10")
|
|
>>> print(f"{result['row_count']} filas en {result['running_time']}ms")
|
|
>>> for row in result["data"]["rows"]:
|
|
... print(row)
|
|
"""
|
|
body: dict = {
|
|
"database": database_id,
|
|
"type": "native",
|
|
"native": {"query": sql},
|
|
}
|
|
if max_results > 0:
|
|
body["constraints"] = {
|
|
"max-results": max_results,
|
|
"max-results-bare-rows": max_results,
|
|
}
|
|
return client.request("POST", "/api/dataset", json=body)
|