centraal-dataframework 0.1.2__tar.gz → 0.3.0__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.
Files changed (61) hide show
  1. centraal_dataframework-0.3.0/PKG-INFO +368 -0
  2. centraal_dataframework-0.3.0/README.md +328 -0
  3. centraal_dataframework-0.3.0/centraal_dataframework/__init__.py +41 -0
  4. centraal_dataframework-0.3.0/centraal_dataframework/__main__.py +5 -0
  5. centraal_dataframework-0.3.0/centraal_dataframework/blueprints.py +268 -0
  6. centraal_dataframework-0.3.0/centraal_dataframework/capacidad.py +157 -0
  7. centraal_dataframework-0.3.0/centraal_dataframework/cli.py +164 -0
  8. centraal_dataframework-0.3.0/centraal_dataframework/config.py +156 -0
  9. centraal_dataframework-0.3.0/centraal_dataframework/email_sender.py +5 -0
  10. centraal_dataframework-0.3.0/centraal_dataframework/excepciones.py +138 -0
  11. centraal_dataframework-0.3.0/centraal_dataframework/gx_synapse.py +91 -0
  12. centraal_dataframework-0.3.0/centraal_dataframework/history.py +156 -0
  13. centraal_dataframework-0.3.0/centraal_dataframework/notifiers.py +203 -0
  14. centraal_dataframework-0.3.0/centraal_dataframework/particiones.py +110 -0
  15. centraal_dataframework-0.3.0/centraal_dataframework/registry.py +205 -0
  16. centraal_dataframework-0.3.0/centraal_dataframework/resources.py +598 -0
  17. centraal_dataframework-0.3.0/centraal_dataframework/runner.py +350 -0
  18. centraal_dataframework-0.3.0/centraal_dataframework/settings.py +192 -0
  19. centraal_dataframework-0.3.0/centraal_dataframework/synapse.py +285 -0
  20. centraal_dataframework-0.3.0/centraal_dataframework/tasks.py +144 -0
  21. centraal_dataframework-0.3.0/centraal_dataframework/templates.py +249 -0
  22. centraal_dataframework-0.3.0/centraal_dataframework/utils.py +62 -0
  23. centraal_dataframework-0.3.0/pyproject.toml +112 -0
  24. centraal_dataframework-0.3.0/tests/conftest.py +82 -0
  25. centraal_dataframework-0.3.0/tests/test_blueprints.py +311 -0
  26. centraal_dataframework-0.3.0/tests/test_blueprints_extra.py +75 -0
  27. centraal_dataframework-0.3.0/tests/test_capacidad.py +145 -0
  28. centraal_dataframework-0.3.0/tests/test_cli.py +125 -0
  29. centraal_dataframework-0.3.0/tests/test_config.py +92 -0
  30. centraal_dataframework-0.3.0/tests/test_email_sender_compat.py +9 -0
  31. centraal_dataframework-0.3.0/tests/test_excepciones_dq.py +21 -0
  32. centraal_dataframework-0.3.0/tests/test_gx_synapse.py +81 -0
  33. centraal_dataframework-0.3.0/tests/test_history.py +179 -0
  34. centraal_dataframework-0.3.0/tests/test_notifiers.py +84 -0
  35. centraal_dataframework-0.3.0/tests/test_notifiers_extra.py +47 -0
  36. centraal_dataframework-0.3.0/tests/test_particiones.py +104 -0
  37. centraal_dataframework-0.3.0/tests/test_registry.py +176 -0
  38. centraal_dataframework-0.3.0/tests/test_resources.py +35 -0
  39. centraal_dataframework-0.3.0/tests/test_resources_gx.py +500 -0
  40. centraal_dataframework-0.3.0/tests/test_runner.py +163 -0
  41. centraal_dataframework-0.3.0/tests/test_runner_extra.py +58 -0
  42. centraal_dataframework-0.3.0/tests/test_settings.py +42 -0
  43. centraal_dataframework-0.3.0/tests/test_synapse.py +468 -0
  44. centraal_dataframework-0.3.0/tests/test_synapse_integration.py +196 -0
  45. centraal_dataframework-0.3.0/tests/test_tasks.py +316 -0
  46. centraal_dataframework-0.3.0/tests/test_utils.py +45 -0
  47. centraal_dataframework-0.1.2/PKG-INFO +0 -212
  48. centraal_dataframework-0.1.2/README.md +0 -164
  49. centraal_dataframework-0.1.2/centraal_dataframework/__init__.py +0 -13
  50. centraal_dataframework-0.1.2/centraal_dataframework/blueprints.py +0 -74
  51. centraal_dataframework-0.1.2/centraal_dataframework/email_sender.py +0 -59
  52. centraal_dataframework-0.1.2/centraal_dataframework/excepciones.py +0 -57
  53. centraal_dataframework-0.1.2/centraal_dataframework/resources.py +0 -148
  54. centraal_dataframework-0.1.2/centraal_dataframework/runner.py +0 -139
  55. centraal_dataframework-0.1.2/centraal_dataframework/tasks.py +0 -46
  56. centraal_dataframework-0.1.2/centraal_dataframework/utils.py +0 -18
  57. centraal_dataframework-0.1.2/pyproject.toml +0 -110
  58. centraal_dataframework-0.1.2/tests/test_runner.py +0 -28
  59. centraal_dataframework-0.1.2/tests/test_utils.py +0 -10
  60. {centraal_dataframework-0.1.2 → centraal_dataframework-0.3.0}/LICENSE +0 -0
  61. {centraal_dataframework-0.1.2 → centraal_dataframework-0.3.0}/tests/__init__.py +0 -0
@@ -0,0 +1,368 @@
1
+ Metadata-Version: 2.4
2
+ Name: centraal_dataframework
3
+ Version: 0.3.0
4
+ Summary: Framework para ejecutar transformaciones y calidad de datos sobre Azure Functions.
5
+ License: Apache-2.0
6
+ License-File: LICENSE
7
+ Author: centraal.studio
8
+ Author-email: equipo@centraal.studio
9
+ Requires-Python: >=3.10,<3.15
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Provides-Extra: synapse
21
+ Requires-Dist: azure-data-tables (>=12.5,<13.0)
22
+ Requires-Dist: azure-datalake-utils (>=0.6.0,<1.0)
23
+ Requires-Dist: azure-functions (>=1.17,<2.0) ; python_version < "3.13"
24
+ Requires-Dist: azure-functions (>=2.0,<3.0) ; python_version >= "3.13"
25
+ Requires-Dist: azure-identity (>=1.15,<2.0)
26
+ Requires-Dist: azure-storage-queue (>=12.9,<13.0)
27
+ Requires-Dist: croniter (>=2.0,<3.0)
28
+ Requires-Dist: great-expectations (>=1.24,<1.25)
29
+ Requires-Dist: numpy (>=1.26,<3.0)
30
+ Requires-Dist: pandas (>=2.2,<3.0)
31
+ Requires-Dist: pydantic (>=2.0,<3.0)
32
+ Requires-Dist: pydantic-settings (>=2.0,<3.0)
33
+ Requires-Dist: pyodbc (>=5.1,<6.0) ; extra == "synapse"
34
+ Requires-Dist: pyyaml (>=6.0,<7.0)
35
+ Requires-Dist: requests (>=2.28,<3.0)
36
+ Requires-Dist: sqlalchemy (>=2.0,<3.0) ; extra == "synapse"
37
+ Project-URL: Homepage, https://github.com/centraal-api/centraal-dataframework
38
+ Description-Content-Type: text/markdown
39
+
40
+ # centraal-dataframework
41
+
42
+ Librería Python para ejecutar transformaciones (pandas) y calidad de datos
43
+ ([Great Expectations](https://greatexpectations.io/) 1.x) sobre
44
+ [Azure Functions](https://azure.microsoft.com/products/functions), con
45
+ programación por YAML, colas, alertas y CLI local.
46
+
47
+ > Versión actual: **0.2.0** (API con `TaskContext`). Guía:
48
+ > [Migración 0.1 → 0.2](docs/MIGRATION_0.1_to_0.2.md).
49
+
50
+ ## Instalación
51
+
52
+ ```bash
53
+ pip install centraal-dataframework
54
+ ```
55
+
56
+ Requiere **Python 3.10–3.14** y `azure-datalake-utils>=0.6.0`.
57
+
58
+ Ese rango coincide con el runtime 4.x de Azure Functions, donde
59
+ [3.10, 3.11, 3.12, 3.13 y 3.14 están en GA](https://learn.microsoft.com/en-us/azure/azure-functions/functions-versions?pivots=programming-language-python).
60
+
61
+ En el plan **Linux Consumption**, 3.12 es la última versión: 3.13 y 3.14 no se agregan ahí. Para esas versiones hay que usar Flex Consumption, Premium o Dedicated.
62
+
63
+ **Despliegue soportado: código en plan Premium (Elastic Premium, Linux) con Python 3.11 o 3.12, y la librería
64
+ desde PyPI con versión fija** (`centraal-dataframework[synapse]==<versión>` en `requirements.txt`).
65
+ `centraal-dataframework init` genera el proyecto y `infra/main.bicep` para ese plan. Guía paso a paso, incluido el
66
+ driver ODBC: [docs/DESPLIEGUE_FUNCTION.md](docs/DESPLIEGUE_FUNCTION.md). Desplegar como contenedor propio no está
67
+ soportado ([referencia](docs/DESPLIEGUE_CONTENEDOR.md)).
68
+ - La imagen de Functions trae el *ODBC Driver 18* desde Python 3.11; 3.10 trae el 17.
69
+ - No hay límite de 10 min por ejecución.
70
+ - La Function entra a Synapse con identidad administrada.
71
+ - Arranca con una instancia siempre encendida, sin escalado elástico ni pre-calentada, y ejecuta las
72
+ tareas en serie. El timer `monitor_capacidad` avisa con `CAPACIDAD:` si hace falta otra instancia
73
+ o una más grande.
74
+ - Regla: **N instancias = N tareas independientes a la vez.** Ver [docs/CAPACIDAD.md](docs/CAPACIDAD.md).
75
+
76
+ En Flex Consumption no se pueden instalar paquetes del sistema si el driver faltara. Después de cada
77
+ despliegue, revise `GET /api/diag`: responde 503 si una tarea perdió su driver o su configuración.
78
+
79
+ ## Variables de entorno
80
+
81
+ | Variable | Default | Descripción |
82
+ |----------|---------|-------------|
83
+ | `AZURE_STORAGE_CONNECTION_STRING` | — | Connection string del storage/datalake |
84
+ | `STORAGE_ACCOUNT_NAME` | — | Alternativa con Managed Identity. El historial también acepta la conexión por identidad del host (`AzureWebJobsStorage__accountName`) |
85
+ | `STORAGE_CONNECTION_SETTING` | `AzureWebJobsStorage` | App setting del binding de cola |
86
+ | `QUEUE_NAME` | `tareas` | Nombre de la cola |
87
+ | `YAML_CONFIGURACION` | `centraal_dataframework.yaml` | Ruta al YAML |
88
+ | `LOGICAPP_EMAIL_URL` | — | URL HTTP de Logic App (no va en el YAML) |
89
+ | `TIMEZONE` | `America/Bogota` | Zona horaria de programación |
90
+ | `CONTENEDOR_VALIDACIONES` | `calidad-datos` | Contenedor docs/stores GX |
91
+ | `DOCS_PUBLIC_BASE_URL` | — | Base URL pública de data docs |
92
+ | `SOBREESCRIBIR_TAREA` | `true` | Permite re-registrar tareas |
93
+ | `TAREA_DEBE_CONFIGURACION` | `true` | Exige tarea en YAML al ejecutar |
94
+ | `MAX_RETRIES` | `5` | Alinear con `maxDequeueCount` |
95
+ | `CAPACIDAD_COLA_MAX` / `CAPACIDAD_ESPERA_MAX_MIN` / `CAPACIDAD_MEMORIA_PCT` | `5` / `60` / `80` | Umbrales del monitor de capacidad ([docs/CAPACIDAD.md](docs/CAPACIDAD.md)) |
96
+ | `HISTORY_ENABLED` | `false` | Historial en Table Storage |
97
+ | `TEAMS_WEBHOOK_URL` / `SLACK_WEBHOOK_URL` | — | Notificadores extra |
98
+ | `SAS_*` | — | Expiración/IP de SAS para reportes |
99
+
100
+ ## Tareas (API 0.2)
101
+
102
+ ```python
103
+ from centraal_dataframework import TaskContext, task
104
+ import great_expectations as gx
105
+
106
+ @task(uses=['datalake'])
107
+ def transformar(ctx: TaskContext):
108
+ df = ctx.datalake.read_csv("ruta/archivo.csv", sep="|")
109
+ ctx.logger.info("filas=%s params=%s", len(df), ctx.params)
110
+
111
+ @task(uses=['datalake', 'gx'])
112
+ def validar(ctx: TaskContext):
113
+ df = ctx.datalake.read_csv("ruta/archivo.csv", sep="|")
114
+ result = ctx.gx.validate(
115
+ df,
116
+ "ventas",
117
+ [gx.expectations.ExpectColumnValuesToNotBeNull(column="id")],
118
+ )
119
+ return result # DQResult; si falla, el runner lo trata como error de calidad y notifica
120
+ ```
121
+
122
+ ### Recursos del contexto
123
+
124
+ Cada plataforma es un recurso distinto del contexto y cada tarea **declara** cuáles usa con `uses`:
125
+
126
+ | Declaración | Acceso |
127
+ |---|---|
128
+ | `'datalake'` | `ctx.datalake` |
129
+ | `'synapse'` | `ctx.synapse`, `ctx.synapse_for('silver')` |
130
+ | `'gx'` | `ctx.gx` |
131
+ | `'synapse:gold'` | `ctx.resource('synapse', 'gold')`: conexión con nombre |
132
+
133
+ - Usar un recurso no declarado lanza `RecursoNoDeclarado`.
134
+ - Declarar una plataforma no registrada falla al importar las tareas (`RecursoDesconocido`).
135
+ - Si un recurso declarado no tiene configuración, la tarea no se ejecuta (`RecursoNoConfigurado`).
136
+ `python -m centraal_dataframework check-resources -m mi_paquete.tareas` revisa todas las tareas sin
137
+ ejecutarlas y termina con código 1 si falta algo.
138
+ - Declarar no conecta: el cliente se crea al primer uso.
139
+ - Si la tarea devuelve un `DQResult` fallido, el runner lo convierte en `ErrorTareaCalidadDatos` y
140
+ notifica. Llamada directa (`validar()` en un notebook), la tarea devuelve el `DQResult` sin lanzar.
141
+ - En la Function, un fallo de calidad se notifica en el primer intento y **no se reintenta** (los datos
142
+ no cambian entre intentos), y sus dependientes no se ejecutan. Los demás errores se reintentan hasta
143
+ `MAX_RETRIES` y notifican solo en el último intento.
144
+ - Un error de **parámetros** también es definitivo si la tarea lo declara con `ParametrosInvalidos`: el
145
+ runner lo registra como `failed`, avisa una vez (con la regla de `notify_dq`) y la cola **no lo reintenta**
146
+ ni lo manda a `*-poison`. Un `ValueError` común sí se reintenta, porque la librería no sabe de dónde sale.
147
+
148
+ ```python
149
+ from centraal_dataframework import ParametrosInvalidos
150
+
151
+ @task(uses=['synapse'])
152
+ def validar_foto(ctx: TaskContext):
153
+ lote = str(ctx.params.get('lote', ''))
154
+ if not LOTE_ISO.match(lote):
155
+ raise ParametrosInvalidos('validar_foto', f"lote {lote!r} no es una fecha ISO")
156
+ ```
157
+
158
+ **Conexiones con nombre.** `'datalake:curado'` usa solo las variables con sufijo `__CURADO`
159
+ (`AZURE_STORAGE_CONNECTION_STRING__CURADO`, `STORAGE_ACCOUNT_NAME__CURADO`). `'synapse:gold'` usa
160
+ `SYNAPSE_WORKSPACE__GOLD`, `SYNAPSE_DATABASE__GOLD`, etc. Los datos de conexión (cuenta, connection string,
161
+ workspace, servidor, base, usuario y clave) **no** se heredan de la conexión por defecto, para no apuntar
162
+ en silencio a otro lago o workspace. El resto (`SYNAPSE_QUERY_TIMEOUT`, `CONTENEDOR_VALIDACIONES`, `SAS_*`, …)
163
+ se hereda y se puede sobrescribir con el mismo sufijo.
164
+
165
+ **Plataformas propias.** `datalake`, `synapse` y `gx` se registran con la misma API pública:
166
+
167
+ ```python
168
+ from centraal_dataframework import register_resource
169
+
170
+ register_resource('mi_api', lambda settings, ctx: MiApi(...), check=lambda settings: [])
171
+ ```
172
+
173
+ `factory(settings, ctx)` recibe las Settings de la conexión (por defecto o con nombre);
174
+ `check(settings)` devuelve los problemas de configuración sin conectarse. Registre la plataforma antes
175
+ de importar las tareas que la declaran.
176
+
177
+ ## Synapse (serverless primero)
178
+
179
+ La tarea envía SQL a Synapse y recibe resultados pequeños (conteos, metadata). El procesamiento
180
+ pesado ocurre dentro de Synapse; los datos no viajan a la Function.
181
+
182
+ Instalar con el extra y el driver del sistema *ODBC Driver 18 for SQL Server*:
183
+
184
+ ```bash
185
+ pip install "centraal-dataframework[synapse]"
186
+ ```
187
+
188
+ | Variable | Default | Descripción |
189
+ |----------|---------|-------------|
190
+ | `SYNAPSE_WORKSPACE` | — | Workspace; usa `<workspace>-ondemand.sql.azuresynapse.net` (serverless) |
191
+ | `SYNAPSE_SERVER` | — | Servidor explícito (p.ej. pool dedicado); tiene prioridad |
192
+ | `SYNAPSE_DATABASE` | — | Base por defecto |
193
+ | `SYNAPSE_AUTH` | `identity` | `identity` (Managed Identity / `DefaultAzureCredential`) o `sql` |
194
+ | `SYNAPSE_USER` / `SYNAPSE_PASSWORD` | — | Solo con `SYNAPSE_AUTH=sql` (desarrollo local) |
195
+ | `SYNAPSE_QUERY_TIMEOUT` | `1800` | Segundos por consulta (límite de serverless: 30 min) |
196
+ | `SYNAPSE_ODBC_DRIVER` | `ODBC Driver 18 for SQL Server` | Driver ODBC instalado; `check-resources` avisa si no está |
197
+
198
+ ```python
199
+ @task(uses=['synapse'])
200
+ def contar(ctx: TaskContext):
201
+ bronze = ctx.synapse_for('bronze') # otra base, misma conexión/credencial
202
+ n = bronze.scalar("SELECT COUNT(*) FROM demo.ventas WHERE fecha = ?", ctx.params['lote'])
203
+ ctx.logger.info("filas=%s", n)
204
+
205
+ @task(uses=['synapse'])
206
+ def crear_vista(ctx: TaskContext):
207
+ # DDL: sin parámetros ?, se usa {{param}} con lista blanca; lotes separados por GO
208
+ ctx.synapse_for('silver').execute_file('sql/vista.sql', esquema='demo', lote=ctx.params['lote'])
209
+ ```
210
+
211
+ API de `SynapseClient`: `query` (lista de dicts), `scalar`, `execute` (DDL/CETAS), `read_df`
212
+ (DataFrame pequeño), `execute_file(path, **params)` y `for_database(base)`. Los valores de
213
+ `{{param}}` solo admiten letras, números y `_` separados por un `-`, `:` o `.` (fechas, ids).
214
+ Reintenta errores transitorios de conexión y throttling; no reintenta errores de SQL ni de login.
215
+
216
+ **Calidad sin traer los datos (caso principal):** `ctx.gx.validate_table(ctx.synapse, name, 'esquema.vista',
217
+ expectations, particion={'fecha': valor}, modo='sql')` ejecuta la suite de GX en serverless sobre una tabla o
218
+ vista con nombre. En bronze la vista acumula fotos particionadas por carpeta y la tarea valida la de un día con
219
+ `particion`; en silver y gold la tabla candidata ya es un solo lote y se llama sin `particion`. La partición
220
+ declarada se verifica contra la definición de la vista (la columna debe salir de `filepath(n)` sobre la carpeta
221
+ `<columna>=*`); si no lo es, o la vista está particionada y no se declaró, se lanza `ParticionInvalida`
222
+ (`particion='completa'` valida toda la vista a propósito). La vista que se valida debe ser la que define el
223
+ `OPENROWSET` (no se siguen vistas sobre vistas) y se admite una sola columna de partición. Con `modo='pandas'`
224
+ la misma suite valida `SELECT *` de la tabla (filtrado por la partición) traído con `read_df`: las filas viajan a
225
+ la Function, solo para tablas pequeñas. El motor es explícito: si el modo pedido no puede ejecutarse se lanza
226
+ un error, nunca se cambia de motor. Un error técnico de GX (columna inexistente, archivo dañado) es
227
+ `ErrorMotorCalidad`, no un fallo de calidad.
228
+
229
+ **Cruces:** `ctx.gx.validate_query(ctx.synapse, name, query, expectations, params=..., modo='pandas')` valida
230
+ el resultado de un SELECT. En `modo='sql'` solo admite expectativas de tabla (conteo de filas): GX no resuelve
231
+ las columnas de una consulta suelta en SQL Server, así que con expectativas de columna lanza `ValueError`
232
+ que remite a `validate_table` o a pandas. Un cruce que necesite modo sql se publica como vista con nombre.
233
+
234
+ Declare `uses=['synapse', 'gx']`. La identidad que valida necesita `GRANT VIEW DEFINITION` en las bases con
235
+ vistas particionadas (además de `db_datareader`), porque la verificación lee `OBJECT_DEFINITION`.
236
+ `great-expectations` está acotado a `>=1.24,<1.25`: `validate_table` usa un `TableAsset` propio que depende de
237
+ nombres privados de GX; subir la cota es una decisión explícita que pasa por `tests/test_gx_synapse.py` y por
238
+ `pytest -m synapse`. Recomendaciones de costo en [docs/MOTOR_DE_COMPUTO.md](docs/MOTOR_DE_COMPUTO.md); para
239
+ desplegar en una Function, [docs/DESPLIEGUE_FUNCTION.md](docs/DESPLIEGUE_FUNCTION.md).
240
+
241
+ **Prueba de integración:** copie [docs/synapse.env.example](docs/synapse.env.example) a
242
+ `.env.synapse` en la raíz, complete las variables del ambiente y ejecute el notebook
243
+ [docs/03_synapse.ipynb](docs/03_synapse.ipynb) o `pytest -m synapse -v`.
244
+
245
+ | Síntoma | Causa probable | Qué hacer |
246
+ |---|---|---|
247
+ | `Falta pyodbc` | No se instaló el extra | `pip install -e ".[synapse]"` |
248
+ | `IM002` / `Data source name not found` | Falta el driver de `SYNAPSE_ODBC_DRIVER` | Instalar el driver del sistema o ajustar `SYNAPSE_ODBC_DRIVER` al instalado (`pyodbc.drivers()`); en Azure, ver [DESPLIEGUE_FUNCTION.md](docs/DESPLIEGUE_FUNCTION.md#2-el-driver-odbc) |
249
+ | `DefaultAzureCredential failed to retrieve a token` | No hay sesión de Azure CLI | `az login [--tenant <tenant-id>]` |
250
+ | `Client with IP address '...' is not allowed` (40615) | IP fuera del firewall del workspace | `az synapse workspace firewall-rule create ...` |
251
+ | `Login failed for user '<token-identified principal>'` (18456) | Sin usuario en esa base, o tenant distinto | `CREATE USER [...] FROM EXTERNAL PROVIDER` en la base; no reintentar en ráfaga |
252
+ | `Cannot find the CREDENTIAL` / `... is not accessible` | Sin permiso sobre la credencial del lago | `GRANT REFERENCES ON DATABASE SCOPED CREDENTIAL::[...]` |
253
+ | `ParticionInvalida: ... GRANT VIEW DEFINITION` | La identidad lee datos pero no la definición de la vista | `GRANT VIEW DEFINITION TO [...]` en esa base |
254
+ | `EngineEdition` distinto de 11 | No es el endpoint serverless | Usar `SYNAPSE_WORKSPACE` sin `SYNAPSE_SERVER` |
255
+
256
+ ## Notebooks
257
+
258
+ Checklist completo de pruebas antes de publicar (local, notebooks, build y Function Premium): [docs/CHECKLIST_PRUEBAS.md](docs/CHECKLIST_PRUEBAS.md).
259
+
260
+ Uno por plataforma, en [docs/](docs/). Variables locales: [docs/datalake.env.example](docs/datalake.env.example)
261
+ → `.env` y [docs/synapse.env.example](docs/synapse.env.example) → `.env.synapse` (ambos ignorados por git).
262
+
263
+ | Notebook | Contenido |
264
+ |---|---|
265
+ | [01_datalake.ipynb](docs/01_datalake.ipynb) | Leer/escribir CSV y Parquet, link SAS, tareas con `uses=['datalake']`, conexión con nombre, runner |
266
+ | [02_great_expectations.ipynb](docs/02_great_expectations.ipynb) | La misma suite con tres orígenes: DataFrame en memoria, archivo del datalake y Synapse (`validate_table` con partición en `modo='sql'`, donde el cómputo es de Synapse, y `modo='pandas'`; `validate_query` para cruces). Además, fallo de calidad y `silenciar_dq`. Las secciones 1 a 3 corren sin storage |
267
+ | [03_synapse.ipynb](docs/03_synapse.ipynb) | Lecturas, `?` vs `render`, `execute_file`, varias bases, métricas en SQL + GX, runner, pandas/polars |
268
+
269
+ Limpie las salidas antes de commitear (*Clear All Outputs*): muestran datos del ambiente.
270
+
271
+ ## YAML de configuración
272
+
273
+ ```yaml
274
+ emails_notificar:
275
+ - equipo@example.com
276
+ tareas:
277
+ transformar:
278
+ dias: '*'
279
+ horas: 8,12,20
280
+ validar:
281
+ dias: '*'
282
+ horas: '*'
283
+ depends_on: [transformar]
284
+ params:
285
+ fecha: "2024-01-01"
286
+ # cron: "0 8 * * 1-5" # opcional, reemplaza dias/horas
287
+ # enabled: false
288
+ # silenciar_dq: # opcional, ver "Silenciar una alarma de calidad"
289
+ # hasta: 2026-11-15
290
+ # motivo: "Proveedor X envía fechas nulas, ticket #123"
291
+ ```
292
+
293
+ Notas de programación (`dias` = weekday ISO-like: 0=lunes … 6=domingo):
294
+
295
+ - `dias: '*'` y `horas: '*'` → cada hora, todos los días
296
+ - `dias: '*'` y `horas: '12'` → todos los días a las 12
297
+ - `dias: 0,3,6` y `horas: '*'` → lunes, jueves y domingo cada hora
298
+
299
+ ### Silenciar una alarma de calidad
300
+
301
+ Cuando un fallo de calidad ya es conocido (p.ej. un proveedor envía datos malos mientras se corrige),
302
+ `silenciar_dq` lo apaga **por un tiempo**. `hasta` (fecha, incluida, en la zona horaria de la tarea) y
303
+ `motivo` son obligatorios.
304
+
305
+ Mientras está vigente, si la validación falla:
306
+
307
+ - no se lanza error, no se notifica y no se reintenta: la tarea termina como exitosa;
308
+ - las tareas que dependen de ella (`depends_on`) se ejecutan;
309
+ - solo queda en el log: `DQ silenciada hasta 2026-11-15 (motivo): N expectativas fallaron en validar`.
310
+
311
+ Al vencer, la alarma vuelve sola. `validate-config` avisa los silencios vencidos y los que vencen en los
312
+ próximos 7 días. No hay forma de silenciar una validación de manera permanente: si no debe detener nada,
313
+ no debería ser una validación de calidad.
314
+
315
+ ## Function App
316
+
317
+ ```python
318
+ import azure.functions as func
319
+ from centraal_dataframework.blueprints import framework
320
+ from mi_paquete import tareas # noqa: F401
321
+
322
+ app = func.FunctionApp()
323
+ app.register_functions(framework)
324
+ ```
325
+
326
+ Scaffold:
327
+
328
+ ```bash
329
+ python -m centraal_dataframework init ./mi-proyecto
330
+ ```
331
+
332
+ ## CLI local
333
+
334
+ ```bash
335
+ python -m centraal_dataframework validate-config -c centraal_dataframework.yaml
336
+ python -m centraal_dataframework list -m mi_paquete.tareas
337
+ python -m centraal_dataframework run transformar -m mi_paquete.tareas --params '{"fecha":"2024-01-01"}'
338
+ python -m centraal_dataframework next-runs transformar -n 5
339
+ ```
340
+
341
+ ## Arquitectura
342
+
343
+ ```mermaid
344
+ graph TD
345
+ settingsMod[Settings] --> runnerMod[Runner]
346
+ configYaml[YAML pydantic] --> runnerMod
347
+ timerFn[TimerTrigger] --> runnerMod
348
+ httpFn[HttpTrigger] --> queueMsg[Cola JSON]
349
+ runnerMod --> queueMsg
350
+ queueMsg --> queueFn[QueueTrigger]
351
+ queueFn --> runnerMod
352
+ runnerMod --> taskCtx[TaskContext]
353
+ taskCtx --> userTask[Tarea]
354
+ runnerMod --> notifier[Notifier]
355
+ ```
356
+
357
+ ## Seguridad
358
+
359
+ No versionar `centraal_dataframework.yaml` real ni `local.settings.json`.
360
+ Ver [docs/SECURITY_ROTATION.md](docs/SECURITY_ROTATION.md).
361
+
362
+ ## Desarrollo
363
+
364
+ ```bash
365
+ poetry install --with test,dev
366
+ poetry run pytest --cov=centraal_dataframework --cov-branch tests
367
+ ```
368
+