feat: metabase_setup Python, fix list_databases, volumen Docker en init_metabase
Nueva función metabase_setup para setup inicial via API. Fix list_databases que no extraía data del response wrapper. Pipeline init_metabase soporta --mb-volumes para montar SQLite como volumen con fix de permisos automático. Añadido .env a gitignore. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,10 +2,14 @@ from .client import MetabaseClient
|
||||
from .users import metabase_list_users, metabase_get_user, metabase_create_user, metabase_update_user, metabase_deactivate_user
|
||||
from .cards import metabase_list_cards, metabase_get_card, metabase_create_card, metabase_update_card, metabase_delete_card, metabase_execute_card, metabase_execute_query
|
||||
from .dashboards import metabase_list_dashboards, metabase_get_dashboard, metabase_create_dashboard, metabase_update_dashboard, metabase_delete_dashboard
|
||||
from .databases import metabase_list_databases, metabase_add_database, metabase_get_database
|
||||
from .setup import metabase_setup
|
||||
|
||||
__all__ = [
|
||||
"MetabaseClient",
|
||||
"metabase_list_users", "metabase_get_user", "metabase_create_user", "metabase_update_user", "metabase_deactivate_user",
|
||||
"metabase_list_cards", "metabase_get_card", "metabase_create_card", "metabase_update_card", "metabase_delete_card", "metabase_execute_card", "metabase_execute_query",
|
||||
"metabase_list_dashboards", "metabase_get_dashboard", "metabase_create_dashboard", "metabase_update_dashboard", "metabase_delete_dashboard",
|
||||
"metabase_list_databases", "metabase_add_database", "metabase_get_database",
|
||||
"metabase_setup",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
"""CRUD de databases de Metabase."""
|
||||
|
||||
from .client import MetabaseClient
|
||||
|
||||
|
||||
def metabase_list_databases(
|
||||
client: MetabaseClient,
|
||||
include_tables: bool = False,
|
||||
) -> list:
|
||||
"""Lista las databases configuradas en Metabase.
|
||||
|
||||
Endpoint: GET /api/database.
|
||||
|
||||
Args:
|
||||
client: Cliente autenticado.
|
||||
include_tables: Si True, incluye las tablas de cada database en la respuesta.
|
||||
|
||||
Returns:
|
||||
Lista de dicts, cada uno con campos de la database:
|
||||
- id: ID numerico de la database
|
||||
- name: Nombre dado en Metabase
|
||||
- engine: Motor de BD (sqlite, postgres, mysql, etc.)
|
||||
- details: Dict con parametros de conexion
|
||||
- is_full_sync: Si sincroniza el schema automaticamente
|
||||
- tables: Lista de tablas (solo si include_tables=True)
|
||||
|
||||
Example:
|
||||
>>> dbs = metabase_list_databases(client)
|
||||
>>> for db in dbs:
|
||||
... print(db["id"], db["name"], db["engine"])
|
||||
|
||||
>>> dbs = metabase_list_databases(client, include_tables=True)
|
||||
>>> for db in dbs:
|
||||
... print(db["name"], [t["name"] for t in db.get("tables", [])])
|
||||
"""
|
||||
params = {}
|
||||
if include_tables:
|
||||
params["include"] = "tables"
|
||||
result = client.request("GET", "/api/database", params=params)
|
||||
if isinstance(result, dict) and "data" in result:
|
||||
return result["data"]
|
||||
return result
|
||||
|
||||
|
||||
def metabase_add_database(
|
||||
client: MetabaseClient,
|
||||
name: str,
|
||||
engine: str,
|
||||
details: dict,
|
||||
) -> dict:
|
||||
"""Agrega una nueva database a Metabase.
|
||||
|
||||
Endpoint: POST /api/database. Requiere permisos de superusuario.
|
||||
|
||||
Args:
|
||||
client: Cliente autenticado con permisos admin.
|
||||
name: Nombre descriptivo para la database en Metabase.
|
||||
engine: Motor de base de datos. Ejemplos: "sqlite", "postgres",
|
||||
"mysql", "h2", "mongo", "bigquery".
|
||||
details: Dict con parametros de conexion especificos del engine.
|
||||
Para SQLite: {"db": "/ruta/al/archivo.db"}
|
||||
Para Postgres: {"host": "...", "port": 5432, "dbname": "...",
|
||||
"user": "...", "password": "..."}
|
||||
|
||||
Returns:
|
||||
Dict con la database creada, incluyendo el campo "id" asignado
|
||||
por Metabase y todos los campos de configuracion.
|
||||
|
||||
Raises:
|
||||
httpx.HTTPStatusError: 400 si los datos de conexion son invalidos.
|
||||
|
||||
Example:
|
||||
>>> db = metabase_add_database(client, "Mi SQLite", "sqlite", {"db": "/data/ops.db"})
|
||||
>>> print(db["id"], db["name"])
|
||||
"""
|
||||
body = {
|
||||
"name": name,
|
||||
"engine": engine,
|
||||
"details": details,
|
||||
}
|
||||
return client.request("POST", "/api/database", json=body)
|
||||
|
||||
|
||||
def metabase_get_database(client: MetabaseClient, database_id: int) -> dict:
|
||||
"""Obtiene los detalles de una database de Metabase por su ID.
|
||||
|
||||
Endpoint: GET /api/database/:id.
|
||||
|
||||
Args:
|
||||
client: Cliente autenticado.
|
||||
database_id: ID numerico de la database.
|
||||
|
||||
Returns:
|
||||
Dict con campos de la database: id, name, engine, details,
|
||||
is_full_sync, is_on_demand, auto_run_queries, created_at,
|
||||
updated_at, features, etc.
|
||||
|
||||
Raises:
|
||||
httpx.HTTPStatusError: 404 si la database no existe.
|
||||
|
||||
Example:
|
||||
>>> db = metabase_get_database(client, 2)
|
||||
>>> print(db["name"], db["engine"])
|
||||
"""
|
||||
return client.request("GET", f"/api/database/{database_id}")
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: metabase_add_database
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def metabase_add_database(client: MetabaseClient, name: str, engine: str, details: dict) -> dict"
|
||||
description: "Agrega una nueva database a Metabase via POST /api/database. Soporta cualquier engine (sqlite, postgres, mysql, etc.)."
|
||||
tags: [metabase, database, add, create, api, python]
|
||||
uses_functions: []
|
||||
uses_types: [MetabaseClient_go_infra]
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [httpx]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "python/functions/metabase/databases.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
# SQLite
|
||||
db = metabase_add_database(client, "Ops DB", "sqlite", {"db": "/data/operations.db"})
|
||||
print(db["id"])
|
||||
|
||||
# Postgres
|
||||
db = metabase_add_database(client, "Prod PG", "postgres", {
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"dbname": "myapp",
|
||||
"user": "reader",
|
||||
"password": "secret",
|
||||
})
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Requiere permisos de superusuario. El campo `details` depende del engine:
|
||||
para SQLite solo necesita `{"db": "/ruta/archivo.db"}`.
|
||||
Retorna la database creada con su `id` asignado por Metabase.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: metabase_get_database
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def metabase_get_database(client: MetabaseClient, database_id: int) -> dict"
|
||||
description: "Obtiene los detalles de una database de Metabase por su ID. Endpoint: GET /api/database/:id."
|
||||
tags: [metabase, database, get, api, python]
|
||||
uses_functions: []
|
||||
uses_types: [MetabaseClient_go_infra]
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [httpx]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "python/functions/metabase/databases.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
db = metabase_get_database(client, 2)
|
||||
print(db["name"], db["engine"])
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Error 404 si la database no existe. Retorna campos completos incluyendo
|
||||
id, name, engine, details, is_full_sync, auto_run_queries, created_at, features.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: metabase_list_databases
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def metabase_list_databases(client: MetabaseClient, include_tables: bool = False) -> list"
|
||||
description: "Lista las databases configuradas en Metabase. Endpoint: GET /api/database. Soporta incluir tablas con include_tables=True."
|
||||
tags: [metabase, database, list, api, python]
|
||||
uses_functions: []
|
||||
uses_types: [MetabaseClient_go_infra]
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [httpx]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "python/functions/metabase/databases.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
dbs = metabase_list_databases(client)
|
||||
for db in dbs:
|
||||
print(db["id"], db["name"], db["engine"])
|
||||
|
||||
# Con tablas incluidas
|
||||
dbs = metabase_list_databases(client, include_tables=True)
|
||||
for db in dbs:
|
||||
print(db["name"], [t["name"] for t in db.get("tables", [])])
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Si include_tables=True agrega el query param `?include=tables` al request.
|
||||
Retorna lista directa (no paginada) de todas las databases accesibles.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: metabase_setup
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "metabase_setup(base_url: str, admin_email: str, admin_password: str, admin_first_name: str, admin_last_name: str, site_name: str, site_locale: str) -> dict"
|
||||
description: "Ejecuta el setup inicial de una instancia Metabase nueva via POST /api/setup. Obtiene el setup-token automaticamente y crea el usuario admin con preferencias del sitio."
|
||||
tags: [metabase, setup, api, infra]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [httpx]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "python/functions/metabase/setup.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
from metabase.setup import metabase_setup
|
||||
|
||||
result = metabase_setup(
|
||||
base_url="http://localhost:3000",
|
||||
admin_email="admin@fnregistry.local",
|
||||
admin_password="FnRegistry2024!",
|
||||
admin_first_name="Lucas",
|
||||
admin_last_name="Admin",
|
||||
site_name="fn-registry",
|
||||
site_locale="es",
|
||||
)
|
||||
print(result["id"]) # session token
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Solo funciona en instancias sin configurar (setup-token disponible). Si Metabase ya tiene un usuario, lanza RuntimeError.
|
||||
|
||||
El setup-token se obtiene automaticamente de GET /api/session/properties. Una vez usado, Metabase invalida el token.
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Setup inicial de Metabase via API."""
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
def metabase_setup(
|
||||
base_url: str,
|
||||
admin_email: str,
|
||||
admin_password: str,
|
||||
admin_first_name: str = "Admin",
|
||||
admin_last_name: str = "User",
|
||||
site_name: str = "Metabase",
|
||||
site_locale: str = "en",
|
||||
) -> dict:
|
||||
"""Ejecuta el setup inicial de una instancia Metabase nueva.
|
||||
|
||||
Usa el setup-token de una instancia sin configurar para crear el
|
||||
usuario admin y configurar preferencias del sitio. Solo funciona
|
||||
una vez — si Metabase ya tiene un usuario, retorna error.
|
||||
|
||||
Endpoint: POST /api/setup (requiere setup-token valido).
|
||||
|
||||
Args:
|
||||
base_url: URL base de la instancia (ej: "http://localhost:3000").
|
||||
admin_email: Email del usuario admin a crear.
|
||||
admin_password: Password del admin (min 8 chars con complejidad).
|
||||
admin_first_name: Nombre del admin.
|
||||
admin_last_name: Apellido del admin.
|
||||
site_name: Nombre del sitio Metabase.
|
||||
site_locale: Locale del sitio (ej: "es", "en").
|
||||
|
||||
Returns:
|
||||
Dict con el session id del admin recien creado.
|
||||
|
||||
Raises:
|
||||
httpx.HTTPStatusError: 400 si el setup-token es invalido o
|
||||
Metabase ya fue configurado.
|
||||
RuntimeError: Si no se puede obtener el setup-token.
|
||||
|
||||
Example:
|
||||
>>> result = metabase_setup(
|
||||
... "http://localhost:3000",
|
||||
... "admin@example.com",
|
||||
... "SecurePass123!",
|
||||
... site_name="fn-registry",
|
||||
... site_locale="es",
|
||||
... )
|
||||
>>> print(result["id"]) # session token
|
||||
"""
|
||||
url = base_url.rstrip("/")
|
||||
|
||||
# Obtener setup-token
|
||||
resp = httpx.get(f"{url}/api/session/properties")
|
||||
resp.raise_for_status()
|
||||
props = resp.json()
|
||||
setup_token = props.get("setup-token")
|
||||
if not setup_token:
|
||||
raise RuntimeError(
|
||||
"No setup-token disponible. Metabase ya fue configurado "
|
||||
"o has-user-setup es True."
|
||||
)
|
||||
|
||||
# Ejecutar setup
|
||||
resp = httpx.post(
|
||||
f"{url}/api/setup",
|
||||
json={
|
||||
"token": setup_token,
|
||||
"user": {
|
||||
"first_name": admin_first_name,
|
||||
"last_name": admin_last_name,
|
||||
"email": admin_email,
|
||||
"password": admin_password,
|
||||
},
|
||||
"prefs": {
|
||||
"site_name": site_name,
|
||||
"site_locale": site_locale,
|
||||
"allow_tracking": False,
|
||||
},
|
||||
},
|
||||
timeout=30.0,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
Reference in New Issue
Block a user