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:
@@ -141,3 +141,162 @@ def metabase_delete_dashboard(client: MetabaseClient, dashboard_id: int) -> None
|
||||
>>> # Preferir: metabase_update_dashboard(client, 1, archived=True)
|
||||
"""
|
||||
client.request("DELETE", f"/api/dashboard/{dashboard_id}")
|
||||
|
||||
|
||||
def metabase_copy_dashboard(
|
||||
client: MetabaseClient,
|
||||
dashboard_id: int,
|
||||
name: str | None = None,
|
||||
collection_id: int | None = None,
|
||||
description: str | None = None,
|
||||
is_deep_copy: bool = False,
|
||||
) -> dict:
|
||||
"""Crea una copia de un dashboard existente en Metabase.
|
||||
|
||||
Endpoint: POST /api/dashboard/:id/copy. Usa el endpoint nativo de Metabase para
|
||||
duplicar el dashboard junto con su layout de dashcards y parametros.
|
||||
Con is_deep_copy=True tambien clona las cards referenciadas.
|
||||
|
||||
Args:
|
||||
client: Cliente autenticado.
|
||||
dashboard_id: ID del dashboard 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.
|
||||
is_deep_copy: Si True, clona tambien todas las cards referenciadas por el
|
||||
dashboard (deep copy). Si False, la copia referencia las cards originales.
|
||||
|
||||
Returns:
|
||||
Dict con el dashboard nuevo creado por Metabase. Incluye el campo `id`
|
||||
asignado a la copia y el layout de dashcards copiado.
|
||||
|
||||
Example:
|
||||
>>> copy = metabase_copy_dashboard(client, 1)
|
||||
>>> print(copy["id"], copy["name"]) # "Copy of ..."
|
||||
>>> # Deep copy a otra coleccion:
|
||||
>>> copy = metabase_copy_dashboard(client, 1, name="Sales Q2", collection_id=7, is_deep_copy=True)
|
||||
"""
|
||||
body: dict = {"is_deep_copy": is_deep_copy}
|
||||
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/dashboard/{dashboard_id}/copy", json=body)
|
||||
|
||||
|
||||
def metabase_move_dashboard(
|
||||
client: MetabaseClient,
|
||||
dashboard_id: int,
|
||||
collection_id: int | None,
|
||||
) -> dict:
|
||||
"""Mueve un dashboard a otra coleccion.
|
||||
|
||||
Wrapper thin sobre PUT /api/dashboard/:id que solo actualiza collection_id.
|
||||
Equivalente a metabase_update_dashboard(client, id, collection_id=...) pero
|
||||
con intencion explicita y soporte para mover a root con None.
|
||||
|
||||
Endpoint: PUT /api/dashboard/:id.
|
||||
|
||||
Args:
|
||||
client: Cliente autenticado.
|
||||
dashboard_id: ID del dashboard a mover.
|
||||
collection_id: ID de la coleccion destino. None mueve a "Our analytics" (root).
|
||||
|
||||
Returns:
|
||||
Dict con el dashboard actualizado, incluyendo el nuevo collection_id.
|
||||
|
||||
Example:
|
||||
>>> dash = metabase_move_dashboard(client, 1, collection_id=7)
|
||||
>>> print(dash["collection_id"]) # 7
|
||||
>>> # Mover a root:
|
||||
>>> dash = metabase_move_dashboard(client, 1, collection_id=None)
|
||||
"""
|
||||
return client.request("PUT", f"/api/dashboard/{dashboard_id}", json={"collection_id": collection_id})
|
||||
|
||||
|
||||
def metabase_create_dashboard_raw(client: MetabaseClient, payload: dict) -> dict:
|
||||
"""Crea un dashboard en Metabase con payload completo ya construido por el caller.
|
||||
|
||||
Version raw de metabase_create_dashboard. El caller es responsable de
|
||||
construir el payload completo — no se realiza validacion ni transformacion
|
||||
local. Util para flujos "Metabase as code" donde el YAML define todos los
|
||||
campos del dashboard tal como los espera la API.
|
||||
|
||||
COMPORTAMIENTO EN DOS PASOS (limitacion de la API de Metabase):
|
||||
El endpoint POST /api/dashboard NO acepta `dashcards` en el body inicial;
|
||||
solo crea el dashboard vacio. Para añadir cards es necesario un PUT posterior.
|
||||
Esta funcion maneja esa limitacion automaticamente:
|
||||
|
||||
1. Si el payload contiene `dashcards`, se extraen antes del POST.
|
||||
2. POST /api/dashboard crea el dashboard vacio (sin dashcards).
|
||||
3. Si habia dashcards, PUT /api/dashboard/:id con {"dashcards": [...]}
|
||||
las añade al dashboard recien creado.
|
||||
4. Retorna la respuesta del PUT (con dashcards pobladas), o la del POST
|
||||
si el payload original no contenia dashcards.
|
||||
|
||||
Endpoint inicial: POST /api/dashboard.
|
||||
Endpoint secundario (condicional): PUT /api/dashboard/:id.
|
||||
|
||||
El payload puede incluir:
|
||||
- name (str, requerido): nombre del dashboard.
|
||||
- description (str): descripcion.
|
||||
- collection_id (int): ID de coleccion destino.
|
||||
- parameters (list[dict]): filtros/parametros del dashboard.
|
||||
- dashcards (list[dict]): cards posicionadas. Cada dashcard necesita:
|
||||
id (int negativo para nuevas, ej: -1, -2),
|
||||
card_id (int), size_x (int), size_y (int), col (int), row (int).
|
||||
Campos opcionales: visualization_settings, parameter_mappings.
|
||||
- tabs (list[dict]): pestañas del dashboard.
|
||||
- enable_embedding (bool): habilitar embedding publico.
|
||||
- embedding_params (dict): configuracion de embedding.
|
||||
|
||||
Si Metabase devuelve 4xx/5xx en cualquier paso, httpx lanza HTTPStatusError.
|
||||
|
||||
Args:
|
||||
client: Cliente autenticado con sesion activa.
|
||||
payload: Dict con el payload completo del dashboard tal como lo espera
|
||||
la API de Metabase. Puede incluir dashcards.
|
||||
|
||||
Returns:
|
||||
Dict con el dashboard creado. Si habia dashcards, la respuesta es la
|
||||
del PUT final e incluye el campo `dashcards` con las cards posicionadas.
|
||||
Si no habia dashcards, es la respuesta del POST inicial.
|
||||
|
||||
Example:
|
||||
>>> dash = metabase_create_dashboard_raw(client, {
|
||||
... "name": "Sales Overview",
|
||||
... "description": "KPIs de ventas mensuales",
|
||||
... "collection_id": 5,
|
||||
... "parameters": [],
|
||||
... "dashcards": [
|
||||
... {
|
||||
... "id": -1,
|
||||
... "card_id": 42,
|
||||
... "size_x": 6,
|
||||
... "size_y": 4,
|
||||
... "col": 0,
|
||||
... "row": 0,
|
||||
... "visualization_settings": {},
|
||||
... "parameter_mappings": [],
|
||||
... },
|
||||
... ],
|
||||
... })
|
||||
>>> print(dash["id"]) # ID asignado por Metabase
|
||||
>>> print(len(dash["dashcards"])) # 1
|
||||
"""
|
||||
body = {k: v for k, v in payload.items() if k != "dashcards"}
|
||||
dashcards = payload.get("dashcards")
|
||||
|
||||
created = client.request("POST", "/api/dashboard", json=body)
|
||||
|
||||
if dashcards:
|
||||
dashboard_id = created["id"]
|
||||
return client.request(
|
||||
"PUT",
|
||||
f"/api/dashboard/{dashboard_id}",
|
||||
json={"dashcards": dashcards},
|
||||
)
|
||||
|
||||
return created
|
||||
|
||||
Reference in New Issue
Block a user