datagrowth-common 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datagrowth_common-0.3.2/.gitignore +63 -0
- datagrowth_common-0.3.2/CHANGELOG.md +120 -0
- datagrowth_common-0.3.2/PKG-INFO +306 -0
- datagrowth_common-0.3.2/README.md +278 -0
- datagrowth_common-0.3.2/RELEASING.md +53 -0
- datagrowth_common-0.3.2/pyproject.toml +59 -0
- datagrowth_common-0.3.2/scripts/build_tailwind.sh +11 -0
- datagrowth_common-0.3.2/src/datagrowth_common/__init__.py +83 -0
- datagrowth_common-0.3.2/src/datagrowth_common/api/__init__.py +20 -0
- datagrowth_common-0.3.2/src/datagrowth_common/api/errors.py +63 -0
- datagrowth_common-0.3.2/src/datagrowth_common/api/pagination.py +51 -0
- datagrowth_common-0.3.2/src/datagrowth_common/api/users.py +78 -0
- datagrowth_common-0.3.2/src/datagrowth_common/auth/__init__.py +17 -0
- datagrowth_common-0.3.2/src/datagrowth_common/auth/local.py +48 -0
- datagrowth_common-0.3.2/src/datagrowth_common/auth/router.py +253 -0
- datagrowth_common-0.3.2/src/datagrowth_common/auth/session.py +139 -0
- datagrowth_common-0.3.2/src/datagrowth_common/auth/session_hmac.py +190 -0
- datagrowth_common-0.3.2/src/datagrowth_common/auth/supabase.py +144 -0
- datagrowth_common-0.3.2/src/datagrowth_common/auth/supabase_admin.py +164 -0
- datagrowth_common-0.3.2/src/datagrowth_common/logger.py +48 -0
- datagrowth_common-0.3.2/src/datagrowth_common/result.py +14 -0
- datagrowth_common-0.3.2/src/datagrowth_common/shared_models.py +33 -0
- datagrowth_common-0.3.2/src/datagrowth_common/supabase.py +46 -0
- datagrowth_common-0.3.2/src/datagrowth_common/testing/__init__.py +12 -0
- datagrowth_common-0.3.2/src/datagrowth_common/testing/fixtures.py +48 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/__init__.py +4 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/loader.py +55 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/static/css/_tailwind.source.css +41 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/static/css/tailwind.compiled.css +1 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/static/css/tokens.css +113 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/static/js/htmx-events.js +51 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/static/js/theme.js +49 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/static/js/toasts.js +22 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/admin/audit_panel.html +22 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/admin/roles_panel.html +18 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/admin/users_panel.html +52 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/badge.html +28 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/button.html +76 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/card.html +33 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/command_palette.html +44 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/data_table.html +42 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/empty_state.html +27 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/form_field.html +99 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/icon.html +90 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/modal.html +39 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/page_header.html +35 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/paginated_table.html +61 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/sidebar_link.html +21 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/skeleton.html +10 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/theme_toggle.html +27 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/components/toast.html +31 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/layouts/empty.html +25 -0
- datagrowth_common-0.3.2/src/datagrowth_common/ui/templates/dg/layouts/sidebar_shell.html +86 -0
- datagrowth_common-0.3.2/src/datagrowth_common/updater/__init__.py +0 -0
- datagrowth_common-0.3.2/src/datagrowth_common/updater/checker.py +48 -0
- datagrowth_common-0.3.2/tailwind.config.js +82 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Python bytecode / caches
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
|
|
7
|
+
# Distribución / build
|
|
8
|
+
build/
|
|
9
|
+
dist/
|
|
10
|
+
wheels/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
*.egg
|
|
13
|
+
.eggs/
|
|
14
|
+
MANIFEST
|
|
15
|
+
|
|
16
|
+
# Entornos virtuales
|
|
17
|
+
.venv/
|
|
18
|
+
venv/
|
|
19
|
+
env/
|
|
20
|
+
ENV/
|
|
21
|
+
.python-version
|
|
22
|
+
|
|
23
|
+
# Testing / cobertura
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.coverage
|
|
26
|
+
.coverage.*
|
|
27
|
+
htmlcov/
|
|
28
|
+
coverage.xml
|
|
29
|
+
*.cover
|
|
30
|
+
.tox/
|
|
31
|
+
.nox/
|
|
32
|
+
.hypothesis/
|
|
33
|
+
|
|
34
|
+
# Type checkers / linters
|
|
35
|
+
.mypy_cache/
|
|
36
|
+
.ruff_cache/
|
|
37
|
+
.pyre/
|
|
38
|
+
.pytype/
|
|
39
|
+
|
|
40
|
+
# Secretos y configuración local
|
|
41
|
+
.env
|
|
42
|
+
.env.*
|
|
43
|
+
!.env.example
|
|
44
|
+
*.pem
|
|
45
|
+
*.key
|
|
46
|
+
|
|
47
|
+
# IDE / editor
|
|
48
|
+
.idea/
|
|
49
|
+
.vscode/
|
|
50
|
+
*.swp
|
|
51
|
+
*.swo
|
|
52
|
+
.DS_Store
|
|
53
|
+
Thumbs.db
|
|
54
|
+
|
|
55
|
+
# Claude / Cursor caches locales
|
|
56
|
+
.claude/cache/
|
|
57
|
+
.claude/memory/sessions.jsonl
|
|
58
|
+
.claude/memory/corrections.jsonl
|
|
59
|
+
.claude/memory/observations.jsonl
|
|
60
|
+
|
|
61
|
+
# Logs
|
|
62
|
+
*.log
|
|
63
|
+
logs/
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Todas las versiones notables se documentan aquí. El proyecto sigue [SemVer](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.3.2] — 2026-05-11
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **wheel**: eliminado bloque `force-include` redundante que duplicaba todos los archivos de `ui/static/*` y `ui/templates/*` dentro del wheel.
|
|
12
|
+
- **sdist**: anclados los patterns de `include` con `/` para evitar que `README.md` matchee recursivamente (antes arrastraba `template/.claude/memory/README.md` y `template/README.md`).
|
|
13
|
+
- **runtime dep**: añadido `python-multipart>=0.0.9` a `dependencies`. Sin él, `auth_router` (que usa `Form()`) lanzaba `RuntimeError` en la primera request.
|
|
14
|
+
|
|
15
|
+
### Added — repo dual: GitHub template
|
|
16
|
+
|
|
17
|
+
- **`template/`**: scaffold FastAPI listo para apps Datagrowth, instanciable con `gh repo create --template datagrowth/datagrowth-common`.
|
|
18
|
+
- `src/main.py` que llama `register_ui(app, jinja_env)` y monta `auth_router`, `users_router`, `admin_users` y `dashboard`.
|
|
19
|
+
- `src/jinja_env.py` usando `ChoiceLoader` (compatible con `register_ui`).
|
|
20
|
+
- `src/templates/base.html` extiende `dg/layouts/sidebar_shell.html` con marcadores `INSTANCE_TOKENS_OVERRIDE_LINK_*` para que el deployer inyecte el `<link>` al `tokens-override.css` del cliente.
|
|
21
|
+
- `pyproject.toml` con pin `datagrowth-common[supabase]~=0.3.1`.
|
|
22
|
+
- `tests/test_smoke.py`: arranque `/healthz` + assets `/static/dg-common/css/tokens.css` y `/static/dg-common/js/theme.js`.
|
|
23
|
+
- Governance migrada desde `app-backend`: `.claude/` (agents `architect`/`reviewer`/`security-reviewer`, rules core/api-design/performance/security/scratch-files, skills `boot`/`evolution`/`evolve`/`fix-issue`/`find-skills`/`precommit`, commands incluyendo `/remember`, settings.json, HOOKS.md), `.cursor/` (commands `/start`/`/pr`/`/gc`/`/review`, rules sin atlassian).
|
|
24
|
+
- Documentación de onboarding en `README.md`, `PROJECT_SPEC.md`, `PROJECT_STATE.md`, `ARCHITECTURE.md` esqueletos.
|
|
25
|
+
- **`docs/VISUAL_LANGUAGE.md`**: migrado desde `datagrowth-ai-template/.impeccable.md`.
|
|
26
|
+
- **`.github/workflows/release.yml`**: compila Tailwind antes de buildear el wheel y añade job `verify-template` que instala el wheel recién publicado contra `template/` y corre los smoke tests.
|
|
27
|
+
|
|
28
|
+
### Deprecated
|
|
29
|
+
|
|
30
|
+
- `datagrowth-ai-template` queda obsoleto: su rol se asume aquí (template + governance). Archivar el repo antiguo en GitHub.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- `app-backend/src/control/scaffolder.py`: eliminadas las constantes `TEMPLATE_REPO` y `_TEMPLATE_DOCS` que apuntaban a `datagrowth-ai-template`. Nadie las leía.
|
|
35
|
+
|
|
36
|
+
## [0.3.1] — 2026-05-11
|
|
37
|
+
|
|
38
|
+
### Added — subpaquete `datagrowth_common.ui`
|
|
39
|
+
|
|
40
|
+
- **`datagrowth_common.ui`** — subpaquete nuevo que distribuye toda la UI compartida (Jinja2 partials + CSS + JS) dentro del wheel.
|
|
41
|
+
- **`register_ui(app, jinja_env, mount_path="/static/dg-common")`** — helper que añade un `PackageLoader` al `ChoiceLoader` del Jinja Environment del host y monta los assets estáticos del paquete. Devuelve `tuple[None, Exception | None]`.
|
|
42
|
+
- **Constante `UI_VERSION`** exportada en raíz (alineada con `__version__`).
|
|
43
|
+
- **20 templates** con prefijo obligatorio `dg/...`:
|
|
44
|
+
- Layouts: `dg/layouts/sidebar_shell.html`, `dg/layouts/empty.html`.
|
|
45
|
+
- Componentes: `badge`, `button`, `card`, `command_palette`, `data_table`, `empty_state`, `form_field`, `icon` (Lucide SVGs inline), `modal`, `page_header`, `paginated_table`, `sidebar_link`, `skeleton`, `theme_toggle`, `toast`.
|
|
46
|
+
- Admin: `users_panel`, `roles_panel`, `audit_panel`.
|
|
47
|
+
- **Static**:
|
|
48
|
+
- `static/css/tokens.css` con tema light/dark via `[data-theme]` y aliases legacy (`--color-brand-lime` → `--color-brand-primary`).
|
|
49
|
+
- `static/css/_tailwind.source.css` con `@layer components` para las clases `btn-*`, `card-*`, `badge-*`, `table-*`, `form-*`, `sidebar-link*`.
|
|
50
|
+
- `static/css/tailwind.compiled.css` build artifact (regenerado con `bash scripts/build_tailwind.sh`).
|
|
51
|
+
- `static/js/theme.js` — toggle light/dark + persistencia en `localStorage["dg-theme"]`.
|
|
52
|
+
- `static/js/toasts.js` — auto-dismiss para `[data-toast]` con listener `htmx:afterSwap`.
|
|
53
|
+
- `static/js/htmx-events.js` — modales (`openModal`/`closeModal`), atajo ⌘K/Ctrl+K para command palette y toast ante `htmx:responseError`.
|
|
54
|
+
- **`tailwind.config.js`** en raíz del paquete y **`scripts/build_tailwind.sh`** para compilar el CSS antes de tag.
|
|
55
|
+
- **`pyproject.toml`** — añade `jinja2>=3.1` como dependencia core y empaqueta `ui/static` + `ui/templates` en el wheel vía `[tool.hatch.build.targets.wheel.force-include]`.
|
|
56
|
+
- **Tests**: 24 nuevos en `tests/ui/test_register_ui.py` (smoke de mount + Jinja loader, error path, parametrizado sobre los 20 templates, smoke de assets servidos).
|
|
57
|
+
- **Doc**: `docs/UI.md` con quickstart, catálogo de componentes y guía para inyectar tokens del cliente via `tokens_override_href`.
|
|
58
|
+
|
|
59
|
+
### Notes
|
|
60
|
+
|
|
61
|
+
- Subir un MAJOR no aplica: la API pública de auth/core no cambia respecto a v0.3.0. Las apps que ya consumen v0.3.0 pueden migrar a v0.3.1 sin tocar imports existentes.
|
|
62
|
+
- Las apps que quieran adoptar el subpaquete UI solo añaden `register_ui(app, jinja_env=templates.env)` tras instanciar `Jinja2Templates`. Los templates `dg/...` se resuelven como fallback cuando el host no define los suyos.
|
|
63
|
+
|
|
64
|
+
## [0.3.0] — 2026-05-11
|
|
65
|
+
|
|
66
|
+
### BREAKING
|
|
67
|
+
|
|
68
|
+
- **Eliminado** `datagrowth_common.authentik` (parser de cabeceras forward-auth `X-authentik-*`). Importarlo ahora lanza `ImportError`. El archivo se conserva como sentinel hasta v0.4.0.
|
|
69
|
+
- **Eliminado** `datagrowth_common.auth_fastapi` (deps FastAPI `require_auth`, `require_admin`, `AuthUserDep`, `AdminUserDep` basadas en forward-auth). Importarlo lanza `ImportError`.
|
|
70
|
+
- **Renombrado** `datagrowth_common.auth.local` → `datagrowth_common.auth.session_hmac`. Se eliminó toda la integración con Authentik OIDC: `password_login`, `refresh_token`, `userinfo`, `start_oidc`, `oidc_callback`, `register_local`, `magic_link_request`, `password_reset`, `TokenPair`, `OidcStart`. Solo se conserva la cookie HMAC propia (`encode_session`, `decode_session`, `make_session`, `set_session_cookie`, `clear_session_cookie`, `require_session`, `require_group`, `SessionUser`).
|
|
71
|
+
- **Removido** del export de `datagrowth_common.testing`: `mock_authentik_responses`.
|
|
72
|
+
- **Removido** del export raíz: `AuthUser`, `require_auth`, `require_admin`.
|
|
73
|
+
|
|
74
|
+
### Compatibilidad
|
|
75
|
+
|
|
76
|
+
- `datagrowth_common.auth.local` se conserva como alias deprecated que re-exporta desde `auth.session_hmac` y emite `DeprecationWarning` en el import. Se eliminará físicamente en v0.4.0.
|
|
77
|
+
- Las apps que generaba el pipeline antiguo (`app-backend/src/agents/prompts/*.md`) seguían referenciando `auth.local`. Esos prompts se reescriben en la Fase I del pivote (apps empaquetadas).
|
|
78
|
+
|
|
79
|
+
### Migration guide
|
|
80
|
+
|
|
81
|
+
| Antes (≤ v0.2.x) | Ahora (v0.3.0) |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `from datagrowth_common.authentik import AuthUser` | `from datagrowth_common.auth.supabase import SupabaseUser` |
|
|
84
|
+
| `from datagrowth_common.auth_fastapi import require_auth, require_admin, AuthUserDep, AdminUserDep` | `from datagrowth_common.auth.session import require_session, SessionUserDep` (cookie JWT) o `from datagrowth_common.auth.supabase import require_supabase_jwt, SupabaseUserDep` (`Authorization: Bearer`) |
|
|
85
|
+
| `from datagrowth_common.auth.local import SessionUser, require_session, encode_session, decode_session, make_session` | `from datagrowth_common.auth.session_hmac import SessionUser, require_session, encode_session, decode_session, make_session` |
|
|
86
|
+
| `from datagrowth_common.auth.local import password_login, oidc_callback, magic_link_request, password_reset, register_local` | Usar `datagrowth_common.auth.router.make_auth_router()` (login email/password + OAuth + magic link + reset via Supabase) |
|
|
87
|
+
| `from datagrowth_common.testing import mock_authentik_responses` | Eliminado. Mockear directamente `httpx.AsyncClient` en el test. |
|
|
88
|
+
|
|
89
|
+
El modelo único de autenticación end-user en v0.3.0 es Supabase Auth:
|
|
90
|
+
|
|
91
|
+
- `auth.supabase` — `verify_supabase_jwt`, `SupabaseUser`, `require_supabase_jwt`, `SupabaseUserDep`, `require_role(role)`.
|
|
92
|
+
- `auth.session` — cookie httpOnly server-rendered con access + refresh JWT (`set_session`, `clear_session`, `refresh_session_tokens`, `require_session`).
|
|
93
|
+
- `auth.router` — `make_auth_router()` con endpoints `POST /auth/password`, `POST /auth/register`, `POST /auth/magic-link/request`, `POST /auth/password/reset`, `GET /auth/oauth/{provider}/start|callback`, `POST /auth/refresh`, `POST /logout`, `GET /api/me`.
|
|
94
|
+
- `auth.session_hmac` — cookie HMAC propia (fallback para apps internas que no usan Supabase Auth).
|
|
95
|
+
|
|
96
|
+
### Notas de despliegue
|
|
97
|
+
|
|
98
|
+
- Apps consumidoras que aún tengan `from datagrowth_common.authentik import ...` o `from datagrowth_common.auth_fastapi import ...` tienen que migrar antes de instalar v0.3.0. El import falla en tiempo de carga del módulo.
|
|
99
|
+
- `DG_ADMIN_GROUP` y `DG_USERS_GROUP` (variables de entorno del legacy forward-auth) dejan de leerse. Borrar del `.env` y de la config de Easypanel.
|
|
100
|
+
|
|
101
|
+
## [0.2.2] — 2026-04-XX
|
|
102
|
+
|
|
103
|
+
- `auth.supabase_admin` añadidos `create_user_with_password` y `send_password_reset` para flujos de gestión de usuarios end-to-end desde paneles admin (backend interno + apps generadas).
|
|
104
|
+
|
|
105
|
+
## [0.2.1] — 2026-03-XX
|
|
106
|
+
|
|
107
|
+
- Añadido `auth.session` (cookie httpOnly server-rendered con refresh).
|
|
108
|
+
- Añadido `auth.router` con `auth_router` + `make_auth_router` (login propio + OAuth Microsoft/Google via Supabase + refresh + logout).
|
|
109
|
+
- `auth.local` marcado como deprecated.
|
|
110
|
+
|
|
111
|
+
## [0.2.0] — 2026-02-XX
|
|
112
|
+
|
|
113
|
+
- Añadido `auth.supabase` (`verify_supabase_jwt`).
|
|
114
|
+
- Añadido `auth.supabase_admin` (Admin API: list / invite / delete users).
|
|
115
|
+
- Añadido `api.users.users_router`.
|
|
116
|
+
- Añadido `get_supabase_admin_client`.
|
|
117
|
+
|
|
118
|
+
## [0.1.0] — 2026-01-XX
|
|
119
|
+
|
|
120
|
+
- Primera release pública: `auth.local`, `authentik`, `auth_fastapi`, `result`, `logger`, `supabase`, `shared_models`, `updater`.
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datagrowth-common
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Datagrowth common: Supabase Auth (cookie JWT + Admin API), API helpers, structured logging, result pattern, Supabase client, shared models and Jinja2 + Tailwind UI subpackage.
|
|
5
|
+
Author-email: Datagrowth <pablo.ramos@datagrowth.es>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: auth,datagrowth,fastapi,jinja2,structlog,supabase,tailwind
|
|
8
|
+
Classifier: Framework :: FastAPI
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Requires-Dist: fastapi>=0.115
|
|
13
|
+
Requires-Dist: httpx>=0.28
|
|
14
|
+
Requires-Dist: jinja2>=3.1
|
|
15
|
+
Requires-Dist: pydantic[email]>=2.0
|
|
16
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
17
|
+
Requires-Dist: structlog>=24.4
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: httpx>=0.28; extra == 'dev'
|
|
20
|
+
Requires-Dist: pyjwt>=2.9; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
24
|
+
Provides-Extra: supabase
|
|
25
|
+
Requires-Dist: pyjwt>=2.9; extra == 'supabase'
|
|
26
|
+
Requires-Dist: supabase>=2.0; extra == 'supabase'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# datagrowth-common
|
|
30
|
+
|
|
31
|
+
Repo de doble propósito:
|
|
32
|
+
|
|
33
|
+
1. **Paquete Python** (`src/datagrowth_common/`): auth (Supabase + cookie HMAC), UI (Jinja2 + CSS + JS), logging, helpers de API. Se instala vía pip.
|
|
34
|
+
2. **GitHub template** (`template/`): scaffold FastAPI listo para Datagrowth. Se instancia con `gh repo create --template datagrowth/datagrowth-common`.
|
|
35
|
+
|
|
36
|
+
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).
|
|
37
|
+
|
|
38
|
+
## Quickstart como template (nuevo repo de app)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
gh repo create my-new-app --template datagrowth/datagrowth-common --private
|
|
42
|
+
gh repo clone datagrowth/my-new-app
|
|
43
|
+
cd my-new-app
|
|
44
|
+
cp infra/.env.example .env # rellena las claves Supabase
|
|
45
|
+
docker compose -f infra/docker-compose.yml up -d --wait
|
|
46
|
+
curl http://localhost:8000/healthz
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Tras esto, abre Cursor / Claude Code en el repo y pega el prompt de onboarding del `README.md` del propio repo recién creado.
|
|
50
|
+
|
|
51
|
+
## Quickstart como dependencia pip
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install "datagrowth-common[supabase]~=0.3.1"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
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.
|
|
58
|
+
|
|
59
|
+
## Qué incluye
|
|
60
|
+
|
|
61
|
+
### Supabase Auth (v0.3.0+)
|
|
62
|
+
|
|
63
|
+
| Módulo | Contenido |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `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`). |
|
|
66
|
+
| `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. |
|
|
67
|
+
| `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). |
|
|
68
|
+
| `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`. |
|
|
69
|
+
| `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`. |
|
|
70
|
+
| `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`. |
|
|
71
|
+
| `supabase` | `get_supabase_client(schema)` (anon key) y `get_supabase_admin_client(schema)` (service_role key). |
|
|
72
|
+
|
|
73
|
+
### Genérico
|
|
74
|
+
|
|
75
|
+
| Módulo | Contenido |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `result` | `Result[T]`, `ok()`, `err()` — patrón de error como valor |
|
|
78
|
+
| `logger` | `setup_logging()`, `get_logger()` — structlog preconfigurado (JSON en prod, consola en dev) |
|
|
79
|
+
| `api.errors` | `ApiResponse`, `ErrorBody`, `ErrorCode`, `ok_response`, `err_response`, `HTTP_STATUS_FOR_CODE` |
|
|
80
|
+
| `api.pagination` | `Paginated[T]`, `PageMeta`, `paginate_params`, `build_meta` |
|
|
81
|
+
| `shared_models` | `ClientRead`, `ProjectRead`, `ContactRead` — modelos Pydantic de lectura |
|
|
82
|
+
| `updater.checker` | `check_for_updates()`, `run_periodic_check()` — verificador de versiones async |
|
|
83
|
+
| `testing.fixtures` | `signed_session_cookie`, `mock_supabase_client` y otros fixtures pytest |
|
|
84
|
+
|
|
85
|
+
### Eliminado en v0.3.0 (breaking)
|
|
86
|
+
|
|
87
|
+
| Módulo | Reemplazo |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `authentik` | `auth.supabase` (validación JWT) |
|
|
90
|
+
| `auth_fastapi` | `auth.session.require_session` (cookie JWT) o `auth.supabase.require_supabase_jwt` (Bearer) |
|
|
91
|
+
| `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 |
|
|
92
|
+
|
|
93
|
+
`auth.local` se conserva como alias deprecated (DeprecationWarning) hasta v0.4.0. Ver `CHANGELOG.md` para la guía de migración completa.
|
|
94
|
+
|
|
95
|
+
## Instalación
|
|
96
|
+
|
|
97
|
+
### Pip (editable, desarrollo local)
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pip install -e /ruta/a/datagrowth-common
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Desde GitHub (release pinned)
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install git+https://github.com/datagrowth/datagrowth-common.git@v0.3.0
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Con soporte Supabase Auth
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install "datagrowth-common[supabase] @ git+https://github.com/datagrowth/datagrowth-common.git@v0.3.0"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
El extra `supabase` añade `supabase>=2.0` y `pyjwt>=2.9` (necesarios para `verify_supabase_jwt` y `users_router`).
|
|
116
|
+
|
|
117
|
+
### En `pyproject.toml` de otra app
|
|
118
|
+
|
|
119
|
+
```toml
|
|
120
|
+
[project]
|
|
121
|
+
dependencies = [
|
|
122
|
+
"datagrowth-common[supabase] @ git+https://github.com/datagrowth/datagrowth-common.git@v0.3.0",
|
|
123
|
+
]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Uso rápido — Supabase Auth en una app FastAPI
|
|
127
|
+
|
|
128
|
+
### Modo API (cliente JS o app móvil envía `Authorization: Bearer`)
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from fastapi import FastAPI
|
|
132
|
+
|
|
133
|
+
from datagrowth_common import (
|
|
134
|
+
setup_logging, get_logger,
|
|
135
|
+
SupabaseUserDep, require_role,
|
|
136
|
+
)
|
|
137
|
+
from datagrowth_common.api.users import users_router
|
|
138
|
+
|
|
139
|
+
setup_logging()
|
|
140
|
+
log = get_logger(__name__)
|
|
141
|
+
|
|
142
|
+
app = FastAPI()
|
|
143
|
+
|
|
144
|
+
# Endpoints CRUD de usuarios sobre Supabase Admin API:
|
|
145
|
+
# GET /api/users (admin, paginado)
|
|
146
|
+
# POST /api/users/invite (admin)
|
|
147
|
+
# DELETE /api/users/{id} (admin)
|
|
148
|
+
# GET /api/users/me (cualquier user autenticado)
|
|
149
|
+
app.include_router(users_router)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@app.get("/leads")
|
|
153
|
+
async def list_leads(user: SupabaseUserDep) -> dict:
|
|
154
|
+
log.info("leads", user=user.id, role=user.role)
|
|
155
|
+
return {"data": [...]}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@app.delete("/leads/{lead_id}")
|
|
159
|
+
async def delete_lead(lead_id: str, _: object = require_role("admin")):
|
|
160
|
+
return {"deleted": lead_id}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
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`.
|
|
164
|
+
|
|
165
|
+
### Modo server-rendered (Jinja + HTMX + cookie httpOnly)
|
|
166
|
+
|
|
167
|
+
Para apps donde el navegador navega entre páginas HTML (no SPA):
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from fastapi import FastAPI, Request
|
|
171
|
+
from fastapi.responses import HTMLResponse
|
|
172
|
+
|
|
173
|
+
from datagrowth_common import (
|
|
174
|
+
SessionUserDep, make_auth_router, setup_logging, get_logger,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
setup_logging()
|
|
178
|
+
log = get_logger(__name__)
|
|
179
|
+
app = FastAPI()
|
|
180
|
+
|
|
181
|
+
# Router con login propio + OAuth + refresh + logout. La página HTML de
|
|
182
|
+
# /login la sirves tú con tu propio branding; el router solo expone los
|
|
183
|
+
# endpoints API.
|
|
184
|
+
app.include_router(make_auth_router(
|
|
185
|
+
post_login_redirect="/",
|
|
186
|
+
post_logout_redirect="/login",
|
|
187
|
+
))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@app.get("/login", response_class=HTMLResponse)
|
|
191
|
+
async def login_page(request: Request, error: str | None = None):
|
|
192
|
+
return templates.TemplateResponse(request, "login.html", {"error": error})
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@app.get("/")
|
|
196
|
+
async def home(user: SessionUserDep) -> dict:
|
|
197
|
+
return {"email": user.email}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`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.
|
|
201
|
+
|
|
202
|
+
## Variables de entorno
|
|
203
|
+
|
|
204
|
+
### Supabase Auth (v0.2.1+)
|
|
205
|
+
|
|
206
|
+
| Variable | Descripción | Por defecto |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| `SUPABASE_URL` | URL del proyecto Supabase (cloud o self-hosted) | — |
|
|
209
|
+
| `SUPABASE_KEY` | Anon/publishable key (lectura+RLS) | — |
|
|
210
|
+
| `SUPABASE_SERVICE_ROLE_KEY` | Service role key (Admin API). **NO exponer al frontend** | — |
|
|
211
|
+
| `SUPABASE_JWT_SECRET` | Secreto HS256 (Project Settings → API). Usado por `verify_supabase_jwt` | — |
|
|
212
|
+
| `SUPABASE_JWT_AUDIENCE` | Audience esperada en el JWT | `authenticated` |
|
|
213
|
+
| `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 |
|
|
214
|
+
| `AUTH_REGISTRATION` | `open` permite que cualquiera se registre via `POST /auth/register`; cualquier otro valor lo bloquea (403) | `invite_only` |
|
|
215
|
+
| `AUTH_MAGIC_LINK` | `true` habilita `POST /auth/magic-link/request` (OTP via email) | `false` |
|
|
216
|
+
| `DG_ENV` | `development` permite cookies sin `Secure` para tests locales sin TLS | `production` |
|
|
217
|
+
|
|
218
|
+
### Genéricas
|
|
219
|
+
|
|
220
|
+
| Variable | Descripción | Por defecto |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `DG_ENV` | `development` permite cookies sin `Secure` para tests locales sin TLS | `production` |
|
|
223
|
+
| `LOG_LEVEL` | `DEBUG`, `INFO`, `WARNING`, `ERROR` | `INFO` |
|
|
224
|
+
| `DG_APP_SLUG` | Slug de la app para el checker de actualizaciones | — |
|
|
225
|
+
| `DG_TENANT_SLUG` | Slug del tenant para el checker de actualizaciones | — |
|
|
226
|
+
| `DG_APP_VERSION` | Versión actual de la app | `0.0.0` |
|
|
227
|
+
| `DG_RELEASES_URL` | URL base del backend de releases | `https://backend.dev.datagrowth.es` |
|
|
228
|
+
|
|
229
|
+
### Cookie HMAC propia (`auth.session_hmac`, opcional)
|
|
230
|
+
|
|
231
|
+
| Variable | Descripción | Por defecto |
|
|
232
|
+
|---|---|---|
|
|
233
|
+
| `DG_SESSION_SECRET` | Secreto HMAC para firmar la cookie (≥32 bytes) | — |
|
|
234
|
+
| `DG_SESSION_COOKIE` | Nombre de la cookie | `dg_session` |
|
|
235
|
+
| `DG_SESSION_TTL_SEC` | TTL de la sesión en segundos | `86400` |
|
|
236
|
+
|
|
237
|
+
## Patrón Result
|
|
238
|
+
|
|
239
|
+
Evita excepciones flotantes retornando el error como valor:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from datagrowth_common import ok, err, Result
|
|
243
|
+
|
|
244
|
+
async def fetch_data(id: str) -> Result[dict]:
|
|
245
|
+
try:
|
|
246
|
+
data = await some_call(id)
|
|
247
|
+
return ok(data)
|
|
248
|
+
except Exception as e:
|
|
249
|
+
return err(e)
|
|
250
|
+
|
|
251
|
+
data, error = await fetch_data("123")
|
|
252
|
+
if error:
|
|
253
|
+
log.error("fallo fetch", error=str(error))
|
|
254
|
+
else:
|
|
255
|
+
print(data)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
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.
|
|
259
|
+
|
|
260
|
+
## Respuesta API estándar
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from datagrowth_common.api.errors import ApiResponse, ErrorCode, ok_response, err_response
|
|
264
|
+
|
|
265
|
+
@app.post("/items")
|
|
266
|
+
async def create(body: ItemCreate) -> ApiResponse:
|
|
267
|
+
item, exc = await service.create(body)
|
|
268
|
+
if exc:
|
|
269
|
+
return err_response(ErrorCode.INTERNAL_ERROR, "create-failed")
|
|
270
|
+
return ok_response(item)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`ApiResponse` enforza XOR entre `data` y `error` (no se puede tener ambos). El cliente decide éxito leyendo `body.error == null`.
|
|
274
|
+
|
|
275
|
+
## Checker de actualizaciones
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
from datagrowth_common.updater.checker import run_periodic_check
|
|
279
|
+
|
|
280
|
+
def notify(update: dict) -> None:
|
|
281
|
+
print(f"Nueva version disponible: {update['latest']}")
|
|
282
|
+
|
|
283
|
+
# Llamar en el startup de la app o en un background task
|
|
284
|
+
await run_periodic_check(notify, app_slug="fable", tenant_slug="acme")
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Tests
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
pip install -e ".[dev,supabase]"
|
|
291
|
+
pytest -q
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Cobertura actual (v0.3.0): tests sobre cookie HMAC (`auth.session_hmac`), validación JWT Supabase, router CRUD de usuarios, cookie httpOnly de sesión Supabase, `auth_router` (login + OAuth + refresh + logout) y deprecation warning de `auth.local`.
|
|
295
|
+
|
|
296
|
+
## Versionado y releases
|
|
297
|
+
|
|
298
|
+
Sigue [SemVer](https://semver.org/). 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`).
|
|
299
|
+
|
|
300
|
+
| Versión | Cambios principales |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `v0.3.0` | **BREAKING**. Elimina `authentik`, `auth_fastapi` y toda la integración con Authentik OIDC dentro de `auth.local`. Renombra `auth.local` → `auth.session_hmac` (alias deprecated). Modelo único: Supabase Auth. Ver `CHANGELOG.md` para la guía de migración. |
|
|
303
|
+
| `v0.2.2` | Añade `auth.supabase_admin.create_user_with_password` y `send_password_reset` para flujos de gestión end-to-end. |
|
|
304
|
+
| `v0.2.1` | Añade `auth.session` (cookie httpOnly server-rendered con refresh) y `auth.router` (`auth_router` + `make_auth_router` con login propio + OAuth Microsoft/Google via Supabase + refresh + logout). |
|
|
305
|
+
| `v0.2.0` | Añade `auth.supabase` (verify JWT), `auth.supabase_admin` (Admin API), `api.users.users_router`, `get_supabase_admin_client`. `auth.local` queda como legacy. |
|
|
306
|
+
| `v0.1.0` | Primera release pública: `auth.local`, `authentik`, `auth_fastapi`, `result`, `logger`, `supabase`, `shared_models`, `updater`. |
|