4299482b75
Tres modulos nuevos con funciones CRUD completas: - snippets: list, get, create, update, archive (SQL reutilizable) - notifications: list, create_card_alert, create_dashboard_subscription, update, delete - dashboard_filters: add_dashboard_filter (parameter_mappings sobre cards existentes) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
171 lines
5.5 KiB
Python
171 lines
5.5 KiB
Python
"""CRUD de SQL snippets de Metabase.
|
|
|
|
Un snippet es un fragmento SQL reutilizable que se referencia en queries
|
|
nativas con la sintaxis {{snippet: nombre}}. Ideal para CTEs comunes
|
|
que se repiten en multiples cards.
|
|
"""
|
|
|
|
from .client import MetabaseClient
|
|
|
|
|
|
def metabase_list_snippets(client: MetabaseClient, archived: bool = False) -> list[dict]:
|
|
"""Lista SQL snippets de Metabase.
|
|
|
|
Endpoint: GET /api/native-query-snippet.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
archived: Si True, incluye snippets archivados.
|
|
|
|
Returns:
|
|
Lista de dicts con: id, name, content, description, collection_id,
|
|
creator_id, archived, created_at, updated_at.
|
|
|
|
Example:
|
|
>>> snippets = metabase_list_snippets(client)
|
|
>>> for s in snippets:
|
|
... print(s["name"], len(s["content"]))
|
|
"""
|
|
params = {}
|
|
if archived:
|
|
params["archived"] = "true"
|
|
return client.request("GET", "/api/native-query-snippet", params=params)
|
|
|
|
|
|
def metabase_get_snippet(client: MetabaseClient, snippet_id: int) -> dict:
|
|
"""Obtiene un SQL snippet de Metabase por su ID.
|
|
|
|
Endpoint: GET /api/native-query-snippet/:id.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
snippet_id: ID numerico del snippet.
|
|
|
|
Returns:
|
|
Dict con campos completos del snippet: id, name, content, description,
|
|
collection_id, creator_id, archived, created_at, updated_at.
|
|
|
|
Raises:
|
|
httpx.HTTPStatusError: 404 si el snippet no existe.
|
|
|
|
Example:
|
|
>>> snippet = metabase_get_snippet(client, 42)
|
|
>>> print(snippet["name"], snippet["content"])
|
|
"""
|
|
return client.request("GET", f"/api/native-query-snippet/{snippet_id}")
|
|
|
|
|
|
def metabase_create_snippet(
|
|
client: MetabaseClient,
|
|
name: str,
|
|
content: str,
|
|
description: str = "",
|
|
collection_id: int = 0,
|
|
) -> dict:
|
|
"""Crea un nuevo SQL snippet en Metabase.
|
|
|
|
Endpoint: POST /api/native-query-snippet.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
name: Nombre del snippet. Se usa en queries con {{snippet: nombre}}.
|
|
content: SQL del snippet. Puede ser una CTE completa, una subquery,
|
|
o cualquier fragmento SQL reutilizable.
|
|
description: Descripcion opcional del snippet.
|
|
collection_id: ID de la coleccion donde guardar el snippet.
|
|
Si es 0, se guarda en la raiz (Our analytics).
|
|
|
|
Returns:
|
|
Dict con el snippet creado, incluyendo el campo "id" asignado
|
|
por Metabase.
|
|
|
|
Raises:
|
|
httpx.HTTPStatusError: 400 si el nombre ya existe o el SQL es invalido.
|
|
|
|
Example:
|
|
>>> snippet = metabase_create_snippet(
|
|
... client,
|
|
... "supply_orders_cte",
|
|
... '''WITH supply_full AS (
|
|
... SELECT so.id, so.service_request_id
|
|
... FROM supply_orders so
|
|
... LEFT JOIN service_requests sr ON so.service_request_id = sr.id
|
|
... )''',
|
|
... description="CTE base de supply_orders con JOINs para cruce con NAV",
|
|
... )
|
|
>>> print(snippet["id"], snippet["name"])
|
|
>>> # Usar en una card:
|
|
>>> # "{{snippet: supply_orders_cte}} SELECT ... FROM supply_full"
|
|
"""
|
|
body: dict = {"name": name, "content": content}
|
|
if description:
|
|
body["description"] = description
|
|
if collection_id:
|
|
body["collection_id"] = collection_id
|
|
return client.request("POST", "/api/native-query-snippet", json=body)
|
|
|
|
|
|
def metabase_update_snippet(
|
|
client: MetabaseClient,
|
|
snippet_id: int,
|
|
**fields,
|
|
) -> dict:
|
|
"""Actualiza campos de un SQL snippet en Metabase.
|
|
|
|
Endpoint: PUT /api/native-query-snippet/:id.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
snippet_id: ID numerico del snippet a actualizar.
|
|
**fields: Campos a modificar. Campos validos:
|
|
- name (str): Nuevo nombre del snippet.
|
|
- content (str): Nuevo SQL del snippet.
|
|
- description (str): Nueva descripcion.
|
|
- collection_id (int): Nueva coleccion.
|
|
- archived (bool): True para archivar, False para desarchivar.
|
|
|
|
Returns:
|
|
Dict con el snippet actualizado.
|
|
|
|
Raises:
|
|
httpx.HTTPStatusError: 404 si el snippet no existe.
|
|
httpx.HTTPStatusError: 400 si los campos son invalidos.
|
|
|
|
Example:
|
|
>>> updated = metabase_update_snippet(
|
|
... client, 42,
|
|
... content="WITH supply_full AS (SELECT * FROM supply_orders)",
|
|
... description="Version simplificada",
|
|
... )
|
|
>>> print(updated["updated_at"])
|
|
"""
|
|
valid_fields = {"name", "content", "description", "collection_id", "archived"}
|
|
body = {k: v for k, v in fields.items() if k in valid_fields}
|
|
return client.request("PUT", f"/api/native-query-snippet/{snippet_id}", json=body)
|
|
|
|
|
|
def metabase_archive_snippet(client: MetabaseClient, snippet_id: int) -> dict:
|
|
"""Archiva un SQL snippet en Metabase.
|
|
|
|
Wrapper sobre metabase_update_snippet con archived=True.
|
|
Los snippets archivados no aparecen en el autocomplete de queries
|
|
pero sus referencias existentes siguen funcionando.
|
|
|
|
Endpoint: PUT /api/native-query-snippet/:id.
|
|
|
|
Args:
|
|
client: Cliente autenticado.
|
|
snippet_id: ID numerico del snippet a archivar.
|
|
|
|
Returns:
|
|
Dict con el snippet archivado.
|
|
|
|
Raises:
|
|
httpx.HTTPStatusError: 404 si el snippet no existe.
|
|
|
|
Example:
|
|
>>> result = metabase_archive_snippet(client, 42)
|
|
>>> print(result["archived"]) # True
|
|
"""
|
|
return metabase_update_snippet(client, snippet_id, archived=True)
|