feat(metabase): expansion de funciones Python — documents, collections, permissions, validation

Añade un conjunto amplio de funciones al paquete python/functions/metabase:
- Nuevos modulos: collections.py, documents.py, maintenance.py, permissions.py, validation.py (+ test).
- Ampliacion de cards.py, dashboards.py, client.py e __init__.py para exponer las nuevas operaciones.
- Funciones de documentos (create/get/update/delete/archive/copy/move + comentarios), grupos y memberships, permission/collection graphs, copy/move de cards y dashboards, validacion de MBQL/SQL y payloads, actualizacion segura de dashboards y fix_null_ratio.
- .md por funcion con frontmatter para que fn index los registre.
- Actualiza pyproject.toml y uv.lock con las dependencias resultantes.

Impacto: ampliamente mas cobertura de la API de Metabase desde el registry, reutilizable por apps y analisis. No toca Go ni frontend.
This commit is contained in:
2026-04-13 23:31:42 +02:00
parent e42c59de16
commit 4300f1242d
53 changed files with 5102 additions and 5 deletions
+129
View File
@@ -225,3 +225,132 @@ def metabase_execute_query(
"max-results-bare-rows": max_results,
}
return client.request("POST", "/api/dataset", json=body)
def metabase_copy_card(
client: MetabaseClient,
card_id: int,
name: str | None = None,
collection_id: int | None = None,
description: str | None = None,
) -> dict:
"""Crea una copia de una card/pregunta existente en Metabase.
Endpoint: POST /api/card/:id/copy. Usa el endpoint nativo de Metabase para
duplicar la card, copiando dataset_query, display y visualization_settings.
Los campos name, collection_id y description se pueden sobrescribir via body.
Args:
client: Cliente autenticado.
card_id: ID de la card a copiar.
name: Nombre para la copia. None = Metabase asigna "Copy of <nombre>".
collection_id: Coleccion destino. None = misma coleccion que el original.
description: Descripcion de la copia. None = misma que el original.
Returns:
Dict con la card nueva creada por Metabase. Incluye el campo `id`
asignado a la copia y todos los campos heredados del original.
Example:
>>> copy = metabase_copy_card(client, 42)
>>> print(copy["id"], copy["name"]) # "Copy of ..."
>>> # Copiar a otra coleccion con nombre propio:
>>> copy = metabase_copy_card(client, 42, name="Revenue Q2", collection_id=7)
"""
body: dict = {}
if name is not None:
body["name"] = name
if collection_id is not None:
body["collection_id"] = collection_id
if description is not None:
body["description"] = description
return client.request("POST", f"/api/card/{card_id}/copy", json=body or None)
def metabase_move_card(
client: MetabaseClient,
card_id: int,
collection_id: int | None,
) -> dict:
"""Mueve una card/pregunta a otra coleccion.
Wrapper thin sobre PUT /api/card/:id que solo actualiza collection_id.
Equivalente a metabase_update_card(client, card_id, collection_id=...) pero
con intencion explicita y soporte para mover a root con None.
Endpoint: PUT /api/card/:id.
Args:
client: Cliente autenticado.
card_id: ID de la card a mover.
collection_id: ID de la coleccion destino. None mueve a "Our analytics" (root).
Returns:
Dict con la card actualizada, incluyendo el nuevo collection_id.
Example:
>>> card = metabase_move_card(client, 42, collection_id=7)
>>> print(card["collection_id"]) # 7
>>> # Mover a root:
>>> card = metabase_move_card(client, 42, collection_id=None)
"""
return client.request("PUT", f"/api/card/{card_id}", json={"collection_id": collection_id})
def metabase_create_card_raw(client: MetabaseClient, payload: dict) -> dict:
"""Crea una card en Metabase con payload completo ya construido por el caller.
Version raw de metabase_create_card. El caller es responsable de construir
el payload completo antes de llamar a esta funcion — no se realiza ninguna
validacion ni transformacion local. Util para flujos "Metabase as code"
donde el YAML define todos los campos de la card tal como los espera la API.
Endpoint: POST /api/card.
El payload minimo necesita:
- name (str): nombre de la card.
- dataset_query (dict): query SQL nativa o MBQL.
- display (str): tipo de visualizacion (table, bar, scalar, etc.).
Campos opcionales que esta funcion preserva (a diferencia de metabase_create_card):
- visualization_settings (dict): configuracion detallada del grafico.
- parameters (list[dict]): parametros de la query con template tags.
- parameter_mappings (list[dict]): mapeo de parametros a dashboard filters.
- type (str): "question" (default), "model", "metric".
- collection_id (int): ID de coleccion destino.
- description (str): descripcion de la card.
- archived (bool): estado de archivo inicial.
- enable_embedding (bool): habilitar embedding publico.
- embedding_params (dict): configuracion de embedding.
Si Metabase devuelve 4xx/5xx, httpx lanza HTTPStatusError sin capturar.
Args:
client: Cliente autenticado con sesion activa.
payload: Dict con el payload completo de la card tal como lo espera
la API de Metabase. Se envia sin modificaciones.
Returns:
Dict con la card recien creada. Incluye el campo `id` asignado por
Metabase y todos los campos normalizados (display, dataset_query,
visualization_settings, created_at, etc.).
Example:
>>> card = metabase_create_card_raw(client, {
... "name": "Revenue by Month",
... "dataset_query": {
... "database": 1,
... "type": "native",
... "native": {"query": "SELECT date_trunc('month', created_at), SUM(total) FROM orders GROUP BY 1"},
... },
... "display": "line",
... "visualization_settings": {
... "graph.x_axis.title_text": "Month",
... "graph.y_axis.title_text": "Revenue",
... },
... "description": "Monthly revenue trend",
... "collection_id": 5,
... })
>>> print(card["id"]) # ID asignado por Metabase
"""
return client.request("POST", "/api/card", json=payload)