--- name: metabase_update_permission_graph kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "def metabase_update_permission_graph(client: MetabaseClient, graph: dict) -> dict" description: "Actualiza el grafo de permisos de datos en Metabase. Endpoint: PUT /api/permissions/graph. El campo revision en el graph es obligatorio — el servidor rechaza con 409 si no coincide con el actual." tags: [metabase, permissions, graph, databases, schemas, access-control, update, api, python] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [] params: - name: client desc: "instancia autenticada de MetabaseClient con permisos de superusuario" - name: graph desc: "dict con el grafo completo incluyendo el campo revision actual. Debe obtenerse vía metabase_get_permission_graph justo antes de modificar" output: "dict: nuevo grafo tras la actualización con revision incrementado" tested: false tests: [] test_file_path: "" file_path: "python/functions/metabase/permissions.py" --- ## Ejemplo ```python graph = metabase_get_permission_graph(client) # Dar acceso completo al grupo 3 sobre la database 1 graph["groups"]["3"]["1"] = {"schemas": "all", "native": "write"} updated = metabase_update_permission_graph(client, graph) print("nueva revision:", updated["revision"]) ``` ## Control de concurrencia por revision El campo `graph["revision"]` es el mecanismo de optimistic locking nativo de Metabase. **Patrón obligatorio:** 1. `graph = metabase_get_permission_graph(client)` — GET fresco 2. Modificar `graph["groups"][group_id][db_id] = ...` — editar en memoria 3. `graph = metabase_update_permission_graph(client, graph)` — PUT con revision **Nunca cachear el graph.** Si otro proceso modificó el graph entre el GET y el PUT, Metabase devuelve HTTP 409 Conflict y el caller debe reintentar desde el GET. ### Estructura de permisos por database ```python # Acceso completo con SQL nativo graph["groups"]["3"]["1"] = {"schemas": "all", "native": "write"} # Solo lectura, sin SQL nativo graph["groups"]["3"]["1"] = {"schemas": "all", "native": "none"} # Sin acceso graph["groups"]["3"]["1"] = {"schemas": "none", "native": "none"} # Acceso granular por schema/tabla graph["groups"]["3"]["1"] = { "schemas": { "public": { "orders": {"read": "all"}, "users": {"read": "none"}, } }, "native": "none", } ```