Skip to main content

Datagrowth common: Supabase Auth (cookie JWT + Admin API), API helpers, structured logging, result pattern, Supabase client, shared models and Jinja2 + Tailwind UI subpackage.

Project description

datagrowth-common

Repo de doble propósito:

  1. Paquete Python (src/datagrowth_common/): auth (Supabase + cookie HMAC), UI (Jinja2 + CSS + JS), logging, helpers de API. Se instala vía pip.
  2. GitHub template (template/): scaffold FastAPI listo para Datagrowth. Se instancia con gh repo create --template datagrowth/datagrowth-common.

Utilidades compartidas entre las apps del ecosistema Datagrowth (app-backend, ai-automation, fable, chatbot y las apps de cliente generadas por el pipeline multi-agente).

Quickstart como template (nuevo repo de app)

gh repo create my-new-app --template datagrowth/datagrowth-common --private
gh repo clone datagrowth/my-new-app
cd my-new-app
cp infra/.env.example .env  # rellena las claves Supabase
docker compose -f infra/docker-compose.yml up -d --wait
curl http://localhost:8000/healthz

Tras esto, abre Cursor / Claude Code en el repo y pega el prompt de onboarding del README.md del propio repo recién creado.

Quickstart como dependencia pip

pip install "datagrowth-common[supabase,postgres]>=0.7,<0.8"

A partir de v0.3.0, el único modelo de autenticación es Supabase Auth (cookie httpOnly con JWT). Los helpers contra Authentik (forward-auth y OIDC propio) se eliminaron — ver CHANGELOG.md para la guía de migración. La cookie HMAC propia se conserva en auth.session_hmac como fallback para apps internas que no usan Supabase Auth.

Desde v0.7.15, el item activo del sidebar (.sidebar-link-active) es lima sólido + texto navy en lugar del translúcido al 22% que tenía hasta 0.7.14, alineado con el look del backend orquestador. Resuelve via var(--color-brand-primary-rgb)/var(--color-brand-secondary-rgb), así cada cliente lo personaliza vía sus triplets del tokens-override.css. Desde v0.7.14, las apps generadas sirven correctamente sus assets locales (src/static/brand/tokens-override.css, src/static/css/app.css): el main.py del template ahora monta /static/ apuntando al directorio local del repo además del /static/dg-common/ del paquete que ya monta register_ui. Hasta este fix las apps respondían 404 a tokens-override.css aunque el deployer Datagrowth lo hubiera escrito en su filesystem — el bug fue invisible mientras las utility classes Tailwind bakeaban hex literales (pre-0.7.13), pero se hizo crítico con el refactor var(--color-*-rgb) de 0.7.13. Apps existentes heredan el mount via patcher _patch_main_for_local_static_mount del backend Datagrowth al pulsar "Actualizar common". Desde v0.7.13, el paquete deja de imponer la identidad Datagrowth a sus consumidores: tokens.css declara los brand colors en dos formas paralelas — triplet RGB sin envoltura (--color-brand-primary-rgb: 71 85 105) consumido por las utility classes Tailwind con <alpha-value>, y forma funcional (--color-brand-primary) para CSS directo — con defaults gray-based neutrales. El tailwind-preset.cjs pasa de hex literales (brand.lime: '#b5ff82') a rgb(var(--color-brand-primary-rgb) / <alpha-value>) para que bg-brand-lime/15 y similares respeten el tokens-override.css del cliente. sidebar_shell.html añade dos fallbacks request.state.* (dg_user para el topbar email/admin/salir, dg_tokens_override_href para el <link> al CSS por cliente) que permiten a la host app inyectarlos via dep global. Nuevo manifest setup.json + SETUP.md en el skeleton — el dev del paquete declara campos extra a rellenar al instalar la app (prompts runtime, tokens externos, flags) que el orquestador pinta y persiste. Desde v0.7.12, las apps generadas pueden abrirse a desarrolladores externos sin que rompan el rebranding ni el flow de "Actualizar common": nueva skill dg-customize + regla .claude/rules/managed-files.md que codifican el contrato customizable vs gestionado, setup.sh de bootstrap (symlinks .claude/skills + .cursor/skills.agents/skills) y README rewrite con sección de dev-onboarding. La clase .sidebar-section-label aterriza por fin en el compiled CSS (fix: tailwind.config.js ahora escanea también template/src/templates/**/*.html). Desde v0.7.11, el sidebar admin es consistente en todas las pantallas: la dep populate_template_state del template inyecta request.state.dg_user antes de cada handler HTML, y el partial sidebar_nav.html la lee como fallback cuando el handler concreto no pasa user al context Jinja (caso típico: users_router del paquete). Desde v0.7, las apps generadas aplican sus migrations/*.sql automáticamente en el lifespan via apply_migrations(...). Desde v0.5.0, la gestión de usuarios + RBAC + audit log se monta con make_users_router(catalog, deps, ...) (módulo datagrowth_common.users). Desde v0.3.1, la UI compartida (Jinja2 + Tailwind + JS) se inyecta con register_ui(app, jinja_env) (subpaquete datagrowth_common.ui).

Qué incluye

Supabase Auth (v0.3.0+)

Módulo Contenido
auth.supabase verify_supabase_jwt(token) — valida HS256 + exp + aud. SupabaseUser (modelo Pydantic con id, email, role). require_supabase_jwt (FastAPI dep que lee Authorization: Bearer). SupabaseUserDep (alias tipado). require_role(role) (factory de dep con check de app_metadata.role).
auth.session Cookie httpOnly server-rendered: set_session(response, access_token, refresh_token), clear_session, get_session_tokens, require_session (lee cookie + valida JWT), SessionUserDep, refresh_session_tokens (canjea refresh → nuevos tokens). Pensado para apps Jinja+HTMX donde el cliente NO maneja JWTs en JS.
auth.router make_auth_router(post_login_redirect, post_logout_redirect) y auth_router por defecto. Endpoints reutilizables: POST /auth/password, POST /auth/register, POST /auth/magic-link/request, POST /auth/password/reset, GET /auth/oauth/{provider}/start, GET /auth/oauth/{provider}/callback, POST /auth/refresh, POST /logout, GET /api/me. La página /login HTML la sirve cada app con su template propio (branding).
auth.supabase_admin Wrappers async sobre Supabase Admin API con patrón Result: list_users, invite_user_by_email, delete_user, update_user_role, create_user_with_password, send_password_reset.
auth.session_hmac Cookie de sesión firmada con HMAC propio (DG_SESSION_SECRET). Fallback para apps internas que no usan Supabase Auth. Antes de v0.3.0 se llamaba auth.local.
api.users users_router — APIRouter con GET /api/users (paginado, admin), POST /api/users/invite (admin), DELETE /api/users/{id} (admin), GET /api/users/me.
supabase get_supabase_client(schema) (anon key) y get_supabase_admin_client(schema) (service_role key).

UI compartida (v0.3.1+)

Módulo Contenido
ui register_ui(app, jinja_env, mount_path="/static/dg-common") añade un PackageLoader al ChoiceLoader y monta los assets (tokens.css, tailwind.compiled.css, theme.js, toasts.js, htmx-events.js). UI_VERSION alineado con __version__.
ui/templates/dg/ 20 partials Jinja2 con prefijo obligatorio dg/...: layouts (sidebar_shell, empty), componentes (badge, button, card, command_palette, data_table, empty_state, form_field, icon, modal, page_header, paginated_table, sidebar_link, skeleton, theme_toggle, toast), admin (users_panel, roles_panel, audit_panel).
ui/tailwind-preset.cjs Preset Tailwind empaquetado en el wheel: paleta brand, semantic, surfaces, fonts, radii, shadows + safelist con responsive variants (md:flex, lg:grid-cols-3, …). Importable via importlib.resources para builds de frontend dockerizados.

Gestión de usuarios + RBAC (v0.5.0+)

Módulo Contenido
users make_users_router(catalog, deps, mount_prefix, schema, templates)APIRouter con 13 endpoints: HTML del panel, listado paginado, invite, create con password, set password, update roles, password reset, delete, permisos efectivos, CRUD de roles, audit log.
users.permissions PermissionCatalog({"runs.read": "Ver runs", ...}) declarado en código (la BD solo guarda asignaciones, no el catálogo). make_require_permission(perm) — FastAPI dep con cache por request, deniega por defecto.
users.bootstrap bootstrap_roles(db, system_roles, catalog) idempotente: upserts roles del sistema + poda role_permission huérfanos. Refusa borrar si el catalog está vacío (defensa contra bug de import).
users/migrations/001_users_rbac.sql Crea role, user_role, role_permission, user_audit_log con FKs ON DELETE CASCADE e índices.

Auto-migration (v0.7+)

Módulo Contenido
migrations `apply_migrations(migrations_dir, *, db_url=None) -> tuple[list[str], Exception

Genérico

Módulo Contenido
result Result[T], ok(), err() — patrón de error como valor
logger setup_logging(), get_logger() — structlog preconfigurado (JSON en prod, consola en dev)
api.errors ApiResponse, ErrorBody, ErrorCode, ok_response, err_response, HTTP_STATUS_FOR_CODE
api.pagination Paginated[T], PageMeta, paginate_params, build_meta
shared_models ClientRead, ProjectRead, ContactRead — modelos Pydantic de lectura
updater.checker check_for_updates(), run_periodic_check() — verificador de versiones async
testing.fixtures signed_session_cookie, mock_supabase_client y otros fixtures pytest

Eliminado en v0.3.0 (breaking)

Módulo Reemplazo
authentik auth.supabase (validación JWT)
auth_fastapi auth.session.require_session (cookie JWT) o auth.supabase.require_supabase_jwt (Bearer)
auth.local auth.session_hmac (rename). Las funciones OIDC se eliminan; usa auth.router.make_auth_router() para login + OAuth + magic link + reset via Supabase

auth.local se conserva como alias deprecated (DeprecationWarning) hasta v0.4.0. Ver CHANGELOG.md para la guía de migración completa.

Instalación

Publicado en PyPI: https://pypi.org/project/datagrowth-common/.

pip install "datagrowth-common[supabase,postgres]>=0.7,<0.8"

Extras disponibles:

  • [supabase]supabase>=2.0 + pyjwt>=2.9 (necesario para verify_supabase_jwt, users_router y el módulo users).
  • [postgres]psycopg[binary]>=3.2 (necesario para apply_migrations y para el módulo users, que habla Postgres directo via SQLAlchemy).
  • [dev] — toolchain de tests (pytest, pytest-asyncio, pytest-cov, httpx, pyjwt, psycopg).

En pyproject.toml de otra app

[project]
dependencies = [
    "datagrowth-common[supabase,postgres]>=0.7,<0.8",
]

Editable (desarrollo local del propio paquete)

pip install -e /ruta/a/datagrowth-common

Uso rápido — Supabase Auth en una app FastAPI

Modo API (cliente JS o app móvil envía Authorization: Bearer)

from fastapi import FastAPI

from datagrowth_common import (
    setup_logging, get_logger,
    SupabaseUserDep, require_role,
)
from datagrowth_common.api.users import users_router

setup_logging()
log = get_logger(__name__)

app = FastAPI()

# Endpoints CRUD de usuarios sobre Supabase Admin API:
#   GET    /api/users         (admin, paginado)
#   POST   /api/users/invite  (admin)
#   DELETE /api/users/{id}    (admin)
#   GET    /api/users/me      (cualquier user autenticado)
app.include_router(users_router)


@app.get("/leads")
async def list_leads(user: SupabaseUserDep) -> dict:
    log.info("leads", user=user.id, role=user.role)
    return {"data": [...]}


@app.delete("/leads/{lead_id}")
async def delete_lead(lead_id: str, _: object = require_role("admin")):
    return {"deleted": lead_id}

El cliente envía Authorization: Bearer <jwt> donde el JWT lo emitió la propia Supabase de la app (login local, magic link u OIDC). verify_supabase_jwt valida firma HS256 contra SUPABASE_JWT_SECRET, comprueba exp y aud, y extrae sub, email y app_metadata.role.

Modo server-rendered (Jinja + HTMX + cookie httpOnly)

Para apps donde el navegador navega entre páginas HTML (no SPA):

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse

from datagrowth_common import (
    SessionUserDep, make_auth_router, setup_logging, get_logger,
)

setup_logging()
log = get_logger(__name__)
app = FastAPI()

# Router con login propio + OAuth + refresh + logout. La página HTML de
# /login la sirves tú con tu propio branding; el router solo expone los
# endpoints API.
app.include_router(make_auth_router(
    post_login_redirect="/",
    post_logout_redirect="/login",
))


@app.get("/login", response_class=HTMLResponse)
async def login_page(request: Request, error: str | None = None):
    return templates.TemplateResponse(request, "login.html", {"error": error})


@app.get("/")
async def home(user: SessionUserDep) -> dict:
    return {"email": user.email}

set_session guarda dos cookies (dg_session con el access_token JWT, dg_session_refresh con el refresh_token) httpOnly+Secure+SameSite=Lax. require_session valida la cookie en cada request. Si caduca, llamas a POST /auth/refresh para renovarla.

Uso rápido — UI compartida + RBAC + auto-migration

Una app generada por el template monta los tres módulos en src/main.py:

from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI
from fastapi.templating import Jinja2Templates

from datagrowth_common import (
    apply_migrations, get_logger, make_auth_router, register_ui, setup_logging,
)
from datagrowth_common.users import (
    PermissionCatalog, make_users_router, bootstrap_roles,
)

from src.db import get_session

setup_logging()
log = get_logger(__name__)

_MIGRATIONS_DIR = Path(__file__).resolve().parent.parent / "migrations"

CATALOG = PermissionCatalog({
    "users.manage": "Gestionar usuarios",
    "runs.read": "Ver runs",
})
SYSTEM_ROLES = {
    "admin": list(CATALOG.keys()),  # todo
    "viewer": [],
}


@asynccontextmanager
async def lifespan(app: FastAPI):
    applied, exc = await apply_migrations(_MIGRATIONS_DIR)
    if exc is not None:
        log.error("migrations_failed", error=str(exc)[:500])
        raise exc
    if applied:
        log.info("migrations_applied", count=len(applied), files=applied)

    with next(get_session()) as db:
        bootstrap_roles(db, SYSTEM_ROLES, CATALOG)
        db.commit()
    yield


app = FastAPI(lifespan=lifespan)
templates = Jinja2Templates(directory="src/templates")
register_ui(app, jinja_env=templates.env)  # monta /static/dg-common/* y dg/... templates

app.include_router(make_auth_router(post_login_redirect="/", post_logout_redirect="/login"))
app.include_router(make_users_router(
    catalog=CATALOG,
    deps={"db": get_session},
    mount_prefix="/admin/users",
    schema="public",
    templates=templates,
))

El template oficial (template/) ya viene con esto cableado — incluido el bootstrap de admin desde APP_ADMIN_EMAIL. Ver template/src/main.py y template/src/api/admin_users.py como referencia.

Variables de entorno

Supabase Auth (v0.2.1+)

Variable Descripción Por defecto
SUPABASE_URL URL del proyecto Supabase (cloud o self-hosted)
SUPABASE_KEY Anon/publishable key (lectura+RLS)
SUPABASE_SERVICE_ROLE_KEY Service role key (Admin API). NO exponer al frontend
SUPABASE_JWT_SECRET Secreto HS256 (Project Settings → API). Usado por verify_supabase_jwt
SUPABASE_JWT_AUDIENCE Audience esperada en el JWT authenticated
DG_APP_URL URL pública de la app (sin slash final). Usada por make_auth_router para construir el redirect_to del callback OAuth derivada de request.base_url si vacío
AUTH_REGISTRATION open permite que cualquiera se registre via POST /auth/register; cualquier otro valor lo bloquea (403) invite_only
AUTH_MAGIC_LINK true habilita POST /auth/magic-link/request (OTP via email) false
DG_ENV development permite cookies sin Secure para tests locales sin TLS production

Genéricas

Variable Descripción Por defecto
DG_ENV development permite cookies sin Secure para tests locales sin TLS production
LOG_LEVEL DEBUG, INFO, WARNING, ERROR INFO
DG_APP_SLUG Slug de la app para el checker de actualizaciones
DG_TENANT_SLUG Slug del tenant para el checker de actualizaciones
DG_APP_VERSION Versión actual de la app 0.0.0
DG_RELEASES_URL URL base del backend de releases https://backend.dev.datagrowth.es

Cookie HMAC propia (auth.session_hmac, opcional)

Variable Descripción Por defecto
DG_SESSION_SECRET Secreto HMAC para firmar la cookie (≥32 bytes)
DG_SESSION_COOKIE Nombre de la cookie dg_session
DG_SESSION_TTL_SEC TTL de la sesión en segundos 86400

Postgres (apply_migrations, módulo users, v0.6.0+)

Variable Descripción Por defecto
DG_DATABASE_URL URL completa Postgres (postgresql+psycopg://user:pass@host:port/db). Si está presente, las piezas individuales se ignoran.
POSTGRES_PASSWORD Password del usuario Postgres (alternativa a DG_DATABASE_URL)
DG_SUPABASE_DB_HOST Host de Postgres db
DG_SUPABASE_DB_PORT Puerto de Postgres 5432
DG_SUPABASE_DB_USER Usuario Postgres postgres
DG_SUPABASE_DB_NAME Nombre de la BD postgres
APP_ADMIN_EMAIL Email del admin inicial. El bootstrap resuelve el user_id en Supabase Auth y le asigna el rol admin (aditivo, no revoca otros).

Patrón Result

Evita excepciones flotantes retornando el error como valor:

from datagrowth_common import ok, err, Result

async def fetch_data(id: str) -> Result[dict]:
    try:
        data = await some_call(id)
        return ok(data)
    except Exception as e:
        return err(e)

data, error = await fetch_data("123")
if error:
    log.error("fallo fetch", error=str(error))
else:
    print(data)

Todas las funciones auth.supabase_admin.* y verify_supabase_jwt siguen este patrón: nunca lanzan a través de la frontera de módulo.

Respuesta API estándar

from datagrowth_common.api.errors import ApiResponse, ErrorCode, ok_response, err_response

@app.post("/items")
async def create(body: ItemCreate) -> ApiResponse:
    item, exc = await service.create(body)
    if exc:
        return err_response(ErrorCode.INTERNAL_ERROR, "create-failed")
    return ok_response(item)

ApiResponse enforza XOR entre data y error (no se puede tener ambos). El cliente decide éxito leyendo body.error == null.

Checker de actualizaciones

from datagrowth_common.updater.checker import run_periodic_check

def notify(update: dict) -> None:
    print(f"Nueva version disponible: {update['latest']}")

# Llamar en el startup de la app o en un background task
await run_periodic_check(notify, app_slug="fable", tenant_slug="acme")

Tests

pip install -e ".[dev,supabase]"
pytest -q

Cobertura actual (v0.7.x): cookie HMAC (auth.session_hmac), validación JWT Supabase, router CRUD de usuarios (legacy), cookie httpOnly de sesión Supabase, auth_router (login + OAuth + refresh + logout), deprecation warning de auth.local, smoke tests del subpaquete ui (20 templates + assets), módulo users (catálogo, RBAC, make_users_router, audit log, validaciones SQL), apply_migrations (idempotencia + tracking en dg_internal._dg_applied_migrations).

Versionado y releases

Sigue SemVer. Cambios mayores que rompen API requieren bump major. La release se publica via GitHub Action al hacer git tag vX.Y.Z && git push --tags (ver RELEASING.md).

Versión Cambios principales
v0.7.14 Fix crítico de assets locales en apps generadas: template/src/main.py ahora monta /static/ apuntando a src/static/ del repo (además del /static/dg-common/ que monta register_ui). Sin este mount las apps respondían 404 a /static/brand/tokens-override.css aunque el deployer Datagrowth lo hubiera escrito en su filesystem — bug invisible mientras tailwind.compiled.css bakeaba hex literales (pre-0.7.13), crítico desde 0.7.13 con utilities var(--color-*-rgb). Apps existentes heredan el mount via patcher quirúrgico _patch_main_for_local_static_mount del backend Datagrowth al pulsar "Actualizar common" (idempotente, deja structure-mismatch:no-register-ui si el operador customizó el bootstrap). Encadenado con el patcher _patch_main_for_template_state (0.7.11) en un único commit src/main.py (state dep, local static mount).
v0.7.13 Paquete deja de imponer marca Datagrowth: tokens.css declara brand/semánticos en doble forma (triplet --color-X-rgb para Tailwind con <alpha-value> + alias funcional --color-X para CSS directo) con defaults gray-based neutrales. tailwind-preset.cjs pasa de hex literales a rgb(var(--color-X-rgb) / <alpha-value>)tailwind.compiled.css recompilado: cero hex Datagrowth bakeados, todas las utility classes resuelven la marca del cliente vía CSS vars. sidebar_shell.html añade dos fallbacks request.state.*: dg_user (topbar email/Admin/Salir consistente en páginas servidas por routers del paquete como make_users_router) y dg_tokens_override_href (<link> al CSS override del cliente sin tener que pasarlo en cada TemplateResponse). Nuevo set canónico de 46 vars en el override del cliente (5 brand-rgb + 5 brand funcionales + 2 on-*, 4 surfaces light/dark, 4 text, 2 link, 2 sidebar-active, 8 semánticos-rgb + 8 funcionales, 3 fonts, 1 ring-focus). Nuevo template/setup.json + template/SETUP.md para manifest declarativo de campos extra al instalar la app (consumido por app-backend/src/packaging/setup_manifest.py). Apps existentes: regenerar tokens-override.css desde la galería tras pullear para que las utility classes con alpha (bg-brand-lime/15) recuperen la marca del cliente — sin regenerar caen a gray.
v0.7.12 Apps generadas listas para desarrolladores externos: nueva skill template/.agents/skills/dg-customize con el contrato customizable vs gestionado (qué archivos regenera el backend y cuáles puede tocar el dev), regla template/.claude/rules/managed-files.md que Claude Code carga automáticamente al editar archivos gestionados (src/static/brand/**, infra/Dockerfile, src/api/{deps,auth,admin_users}.py, etc.), template/setup.sh de bootstrap (symlinks .claude/skills + .cursor/skills.agents/skills), template/README.md rewrite con sección "Empezar a desarrollar", template/CLAUDE.md con bloque inicial "Esta es una app generada". Fix de rendering del header "Administración" del sidebar: la clase .sidebar-section-label ya viaja en el compiled CSS (era purgada porque tailwind.config.js no escaneaba template/src/templates/**). Header renombrado a "Administrador" con mb-2/my-3 para más jerarquía visual. Apps existentes: setup.sh + regla en _SYNC_FILES, skill via _SYNC_DIRS_RECURSIVE, rename + spacing via patcher quirúrgico _patch_sidebar_admin_section (idempotente).
v0.7.11 Sidebar admin consistente cross-pantalla en apps generadas. Nueva dep template/src/api/deps.py:populate_template_state que inyecta request.state.dg_user antes de cada handler HTML. main.py del template aplica dependencies=[Depends(populate_template_state)] a dashboard.router, users_router y admin_users.router (no a auth.router, que sirve /login público). partials/sidebar_nav.html añade fallback {% set user = user or request.state.dg_user %} para que el bloque "Administración" funcione cuando el handler (caso users_router del paquete) no pasa user al context Jinja. Apps existentes: el backend Datagrowth ofrece 2 patchers quirúrgicos en update_common_pr.py (_patch_main_for_template_state + _patch_sidebar_for_state_fallback) que viajan en el siguiente "Actualizar common".
v0.7.10 Skills heredadas en template/.agents/skills/ viajan en cada repo nuevo: fastapi, supabase, boot, evolution, evolve, find-skills, fix-issue, modular-design, precommit. El backend Datagrowth las sincroniza vía _SYNC_DIRS_RECURSIVE (auto-discovery — añadir una skill nueva al template basta para que se propague sin tocar la lista de archivos en update_common_pr.py).
v0.7.9 Sidebar activo deriva del color de marca: tokens.css añade --color-sidebar-active-bg (color-mix(in srgb, var(--color-brand-primary) 18%, transparent) en light, 22% en dark) y --color-sidebar-active-text (en dark usa var(--color-brand-primary) para legibilidad sobre superficie navy). .sidebar-link-active deja de hardcodear bg-brand-lime/20 text-brand-dark. Apps con primary distinto al lime ven el sidebar adaptado automáticamente. Fallback admin badge tras primer arranque: require_auth de la app fuerza is_admin=True si el email del JWT coincide con APP_ADMIN_EMAIL (cubre el caso de carrera entre el bootstrap admin del lifespan y el JWT viejo en cookies). tmp/ retirado del template (era residuo del app-backend).
v0.7.8 Fixes visibles en la app desplegada: alias POST /logout añadido (antes 404 — el botón del topbar shared apunta a /logout), dashboard.html arregla {{ user.email }} que se renderizaba literal (era string param a macro), bootstrap_admin_from_env ahora sincroniza app_metadata.role en Supabase Auth tras set_user_roles (sin esto el sidebar admin no aparece aunque la tabla app_role lo marque).
v0.7.7 Bundle de marca local en src/static/brand/ (CSS + meta.json + logo) copiado por el deployer al repo de la instance — cero fetch runtime al backend, cero env vars BRAND_*. Marker INSTANCE_TOKENS_OVERRIDE_LINK eliminado del template. auth.py:login_page lee meta.json desde filesystem. Fix crítico: _get_audience() cae al default "authenticated" cuando SUPABASE_JWT_AUDIENCE está empty/whitespace (antes rompía con "Audience doesn't match" aunque el JWT viniera correcto). SUPABASE_JWT_AUDIENCE, BRAND_CLIENT_NAME, BRAND_LOGO_URL, BRAND_PRIMARY_COLOR eliminadas del .env.example. update_common_pr (backend) filtra estas keys obsoletas del .env.example de apps existentes.
v0.7.6 Tokens semánticos de contraste --color-on-primary / --color-on-secondary en tokens.css. Login standalone usa el token en el botón primario (antes color: #fff hardcoded, ilegible sobre lime). El agente design_to_tokens (backend) genera ambos tokens automáticamente con heurística de luminance. Recomendados, no obligatorios — brand designs viejos siguen funcionando.
v0.7.5 Nueva skill template/.claude/skills/modular-design: contrato de modularidad para todos los elementos de diseño en apps generadas. Cualquier asset visual (colores, logo, tipos, copy de marca) debe ser sustituible sin tocar código vía tokens-override.css del cliente o env vars BRAND_*. Defaults Datagrowth en template/infra/.env.example (BRAND_CLIENT_NAME=Datagrowth, logo apuntando a datagrowth.es) — el control plane sobrescribe con valores del cliente al deployar. Color primario migrado de env var a CSS token (var(--color-brand-primary)).
v0.7.4 apply_migrations blindado contra race conditions cross-process: adquiere pg_advisory_lock antes de leer la tabla de control. Sin esto, dos workers uvicorn entrando al lifespan en paralelo se cargaban en CREATE TYPE con duplicate key ... pg_type_typname_nsp_index. Template infra/Dockerfile baja a --workers 1 como defensa en profundidad. template/src/api/deps.py:require_auth redirige a /login (303) cuando el navegador entra sin sesión, en vez de devolver JSON 401.
v0.7.2 Nuevo datagrowth_common.migrations.apply_migrations(). El template invoca la función en el lifespan: las apps generadas aplican sus migrations/*.sql solas (sin que el control plane tenga que pegar Postgres del cliente). Nueva optional dep [postgres]. Template ajustado: Dockerfile copia migrations/, docker-compose.yml declara env_file: ../.env para cargar el .env que Easypanel materializa en la raíz del repo clonado.
v0.6.x Template adopta el panel RBAC nuevo (make_users_router). Endpoint POST /admin/users/{id}/password/set. Compose del template: build local + red n8n_supabase_default external + sin labels Traefik. Botones admin con bypass para is_admin + fall-through en DG_ENV != production.
v0.5.0 Nuevo módulo datagrowth_common.users: make_users_router, PermissionCatalog, make_require_permission, bootstrap_roles, audit log, migración 001_users_rbac.sql. UI panel reescrita (3 tabs, multi-rol, vanilla JS sin HTMX). Dep nueva sqlalchemy>=2.0.
v0.4.x Tailwind preset distribuido (tailwind-preset.cjs) empaquetado dentro del wheel. Tokens semánticos text-fg/bg-surface-subtle + dark mode con FOUC guard + sync entre pestañas. Token link/link-hover para anchors legibles en dark.
v0.3.x BREAKING (v0.3.0): elimina authentik, auth_fastapi, integración Authentik OIDC. Renombra auth.localauth.session_hmac. Modelo único: Supabase Auth. v0.3.1: subpaquete datagrowth_common.ui (register_ui + 20 templates + tokens). v0.3.2+: repo dual (PyPI + GitHub template).
v0.2.x Añade auth.supabase (verify JWT), auth.supabase_admin (Admin API), auth.session (cookie httpOnly), auth.router (make_auth_router con OAuth Microsoft/Google + refresh).
v0.1.0 Primera release pública: auth.local, authentik, auth_fastapi, result, logger, supabase, shared_models, updater.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

datagrowth_common-0.7.15.tar.gz (106.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

datagrowth_common-0.7.15-py3-none-any.whl (95.8 kB view details)

Uploaded Python 3

File details

Details for the file datagrowth_common-0.7.15.tar.gz.

File metadata

  • Download URL: datagrowth_common-0.7.15.tar.gz
  • Upload date:
  • Size: 106.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for datagrowth_common-0.7.15.tar.gz
Algorithm Hash digest
SHA256 3b42008832a2a0239420ffef357199379ec637a923bb8fa5a78021ac8138474a
MD5 dadcef6203c2abfb984bfe146974d441
BLAKE2b-256 0a50a5a2a9e6ab78a179402dbaadfbe18977bbb320a6cc10535df0199cc8ed74

See more details on using hashes here.

Provenance

The following attestation bundles were made for datagrowth_common-0.7.15.tar.gz:

Publisher: release.yml on datagrowth/datagrowth-common

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file datagrowth_common-0.7.15-py3-none-any.whl.

File metadata

File hashes

Hashes for datagrowth_common-0.7.15-py3-none-any.whl
Algorithm Hash digest
SHA256 48479c0e2bcb77e40ccea1c2a65830587db10172f86fe0ae8ce97fd59eb51b52
MD5 91456ab557b1708b1211f29a58a42ebe
BLAKE2b-256 f3b8cd9b5496f44da07e135cc555e0c9988e4fbe06f2fabd6c0156e019bdbeb0

See more details on using hashes here.

Provenance

The following attestation bundles were made for datagrowth_common-0.7.15-py3-none-any.whl:

Publisher: release.yml on datagrowth/datagrowth-common

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page