"""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)