"""Construye un MetabaseClient autenticado leyendo credenciales desde `pass`. Elimina el patron inline repetido de "leer la credencial de Metabase del password store y montar un cliente autenticado", que hoy se reescribe a mano para dos instancias distintas: - Aurgi: API-key (``metabase/aurgi-api-key``, una sola linea ``mb_...``) -> header ``X-API-KEY``. - Captacion: usuario/password (``captacion/metabase``, multi-linea: primera linea password, linea ``email:`` con el usuario) -> login via ``POST /api/session``. Compone tres funciones del registry: ``pass_get_secret`` (lee el secreto), ``parse_metabase_secret`` (parser puro que distingue api_key vs session) y ``metabase_auth`` / ``MetabaseClient`` (auth). No reimplementa ninguna. """ import sys import os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from infra.pass_get_secret import pass_get_secret from metabase.parse_metabase_secret import parse_metabase_secret from metabase.client import MetabaseClient, metabase_auth # Tope de lineas a leer del secreto. pass_get_secret devuelve una linea por # llamada; leemos hasta este maximo o hasta "out of range" para reconstruir el # texto completo sin reimplementar el subproceso de pass. _MAX_SECRET_LINES = 16 def metabase_client_from_pass( pass_key: str, base_url: str, mode: str = "auto", ) -> MetabaseClient | dict: """Lee credenciales de Metabase de `pass` y devuelve un cliente autenticado. Args: pass_key: ruta del secreto en el password store (p.ej. ``"metabase/aurgi-api-key"`` o ``"captacion/metabase"``). base_url: URL base de la instancia Metabase (p.ej. ``"https://reports.autingo.es"``). mode: ``"api_key"``, ``"session"`` o ``"auto"`` (default). En ``auto`` se detecta el formato del secreto: una sola linea de clave -> api_key; multi-linea con email/usuario -> session. Returns: ``MetabaseClient`` autenticado en exito. En caso de fallo (sin lanzar): ``{"status": "error", "error": str}`` para: secreto no encontrado en pass, formato no parseable, o fallo de autenticacion contra Metabase. Example: >>> client = metabase_client_from_pass( ... "metabase/aurgi-api-key", "https://reports.autingo.es", mode="api_key") >>> client.request("GET", "/api/user/current") # doctest: +SKIP """ secret_text = _read_secret_text(pass_key) if isinstance(secret_text, dict): return secret_text # error dict de pass parsed = parse_metabase_secret(secret_text, mode=mode) if parsed["status"] != "ok": return {"status": "error", "error": parsed["error"]} try: if parsed["mode"] == "api_key": # MetabaseClient detecta "mb_" -> usa header X-API-KEY. return MetabaseClient(base_url, parsed["api_key"]) # mode == "session": login con email/password via POST /api/session. return metabase_auth(base_url, parsed["email"], parsed["password"]) except Exception as exc: # noqa: BLE001 - cualquier fallo de red/auth se reporta return {"status": "error", "error": f"metabase auth failed: {exc}"} def _read_secret_text(pass_key: str) -> str | dict: """Reconstruye el texto multi-linea del secreto via pass_get_secret. Llama a pass_get_secret linea por linea (1-indexed) hasta agotar las lineas ("line N out of range") o llegar al tope. Devuelve el texto unido por ``\\n`` o un dict de error si la primera lectura falla (pass no instalado, entry inexistente, etc.). """ first = pass_get_secret(pass_key, line=1) if first["status"] != "ok": return {"status": "error", "error": first["error"]} lines = [first["value"]] for n in range(2, _MAX_SECRET_LINES + 1): res = pass_get_secret(pass_key, line=n) if res["status"] != "ok": break # out of range = fin del secreto lines.append(res["value"]) return "\n".join(lines)