9a28d08e38
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.
91 lines
2.9 KiB
Python
91 lines
2.9 KiB
Python
"""Cliente base para la API REST de Metabase."""
|
|
|
|
import httpx
|
|
|
|
|
|
class MetabaseClient:
|
|
"""Cliente HTTP para una instancia Metabase.
|
|
|
|
Attributes:
|
|
base_url: URL base sin trailing slash (ej: "http://localhost:3000").
|
|
token: Session token o API key.
|
|
_http: Cliente httpx reutilizable con headers de auth.
|
|
"""
|
|
|
|
def __init__(self, base_url: str, token: str, timeout: float = 120.0) -> None:
|
|
self.base_url = base_url.rstrip("/")
|
|
self.token = token
|
|
# API keys de Metabase empiezan por "mb_" y usan X-API-KEY.
|
|
# Session tokens usan X-Metabase-Session.
|
|
auth_header = "X-API-KEY" if token.startswith("mb_") else "X-Metabase-Session"
|
|
self._http = httpx.Client(
|
|
base_url=self.base_url,
|
|
headers={
|
|
"Content-Type": "application/json",
|
|
auth_header: token,
|
|
},
|
|
timeout=timeout,
|
|
)
|
|
|
|
def request(self, method: str, path: str, **kwargs) -> dict | list | None:
|
|
"""Ejecuta una peticion HTTP contra la API de Metabase.
|
|
|
|
Args:
|
|
method: HTTP method (GET, POST, PUT, DELETE).
|
|
path: Ruta relativa (ej: "/api/user").
|
|
**kwargs: Argumentos extra para httpx (json, params, etc.).
|
|
|
|
Returns:
|
|
Respuesta deserializada como dict/list, o None si el body esta vacio.
|
|
|
|
Raises:
|
|
httpx.HTTPStatusError: Si el status code no es 2xx.
|
|
"""
|
|
resp = self._http.request(method, path, **kwargs)
|
|
resp.raise_for_status()
|
|
if not resp.content:
|
|
return None
|
|
return resp.json()
|
|
|
|
def close(self) -> None:
|
|
"""Cierra el cliente HTTP."""
|
|
self._http.close()
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
self.close()
|
|
|
|
|
|
def metabase_auth(base_url: str, email: str, password: str) -> MetabaseClient:
|
|
"""Autentica contra Metabase con email y password.
|
|
|
|
Crea una sesion via POST /api/session y retorna un MetabaseClient
|
|
con el session token listo para usar. El token expira en 14 dias
|
|
por defecto (configurable con MAX_SESSION_AGE en Metabase).
|
|
|
|
Args:
|
|
base_url: URL base de la instancia (ej: "http://localhost:3000").
|
|
email: Email del usuario Metabase.
|
|
password: Password del usuario.
|
|
|
|
Returns:
|
|
MetabaseClient autenticado con session token.
|
|
|
|
Raises:
|
|
httpx.HTTPStatusError: Si las credenciales son invalidas (401)
|
|
o hay rate limiting.
|
|
|
|
Example:
|
|
>>> client = metabase_auth("http://localhost:3000", "admin@example.com", "pass")
|
|
>>> # client listo para usar con todas las funciones CRUD
|
|
"""
|
|
resp = httpx.post(
|
|
f"{base_url.rstrip('/')}/api/session",
|
|
json={"username": email, "password": password},
|
|
)
|
|
resp.raise_for_status()
|
|
token = resp.json()["id"]
|
|
return MetabaseClient(base_url, token)
|