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.
- centraal_dataframework-0.3.0/PKG-INFO +368 -0
- centraal_dataframework-0.3.0/README.md +328 -0
- centraal_dataframework-0.3.0/centraal_dataframework/__init__.py +41 -0
- centraal_dataframework-0.3.0/centraal_dataframework/__main__.py +5 -0
- centraal_dataframework-0.3.0/centraal_dataframework/blueprints.py +268 -0
- centraal_dataframework-0.3.0/centraal_dataframework/capacidad.py +157 -0
- centraal_dataframework-0.3.0/centraal_dataframework/cli.py +164 -0
- centraal_dataframework-0.3.0/centraal_dataframework/config.py +156 -0
- centraal_dataframework-0.3.0/centraal_dataframework/email_sender.py +5 -0
- centraal_dataframework-0.3.0/centraal_dataframework/excepciones.py +138 -0
- centraal_dataframework-0.3.0/centraal_dataframework/gx_synapse.py +91 -0
- centraal_dataframework-0.3.0/centraal_dataframework/history.py +156 -0
- centraal_dataframework-0.3.0/centraal_dataframework/notifiers.py +203 -0
- centraal_dataframework-0.3.0/centraal_dataframework/particiones.py +110 -0
- centraal_dataframework-0.3.0/centraal_dataframework/registry.py +205 -0
- centraal_dataframework-0.3.0/centraal_dataframework/resources.py +598 -0
- centraal_dataframework-0.3.0/centraal_dataframework/runner.py +350 -0
- centraal_dataframework-0.3.0/centraal_dataframework/settings.py +192 -0
- centraal_dataframework-0.3.0/centraal_dataframework/synapse.py +285 -0
- centraal_dataframework-0.3.0/centraal_dataframework/tasks.py +144 -0
- centraal_dataframework-0.3.0/centraal_dataframework/templates.py +249 -0
- centraal_dataframework-0.3.0/centraal_dataframework/utils.py +62 -0
- centraal_dataframework-0.3.0/pyproject.toml +112 -0
- centraal_dataframework-0.3.0/tests/conftest.py +82 -0
- centraal_dataframework-0.3.0/tests/test_blueprints.py +311 -0
- centraal_dataframework-0.3.0/tests/test_blueprints_extra.py +75 -0
- centraal_dataframework-0.3.0/tests/test_capacidad.py +145 -0
- centraal_dataframework-0.3.0/tests/test_cli.py +125 -0
- centraal_dataframework-0.3.0/tests/test_config.py +92 -0
- centraal_dataframework-0.3.0/tests/test_email_sender_compat.py +9 -0
- centraal_dataframework-0.3.0/tests/test_excepciones_dq.py +21 -0
- centraal_dataframework-0.3.0/tests/test_gx_synapse.py +81 -0
- centraal_dataframework-0.3.0/tests/test_history.py +179 -0
- centraal_dataframework-0.3.0/tests/test_notifiers.py +84 -0
- centraal_dataframework-0.3.0/tests/test_notifiers_extra.py +47 -0
- centraal_dataframework-0.3.0/tests/test_particiones.py +104 -0
- centraal_dataframework-0.3.0/tests/test_registry.py +176 -0
- centraal_dataframework-0.3.0/tests/test_resources.py +35 -0
- centraal_dataframework-0.3.0/tests/test_resources_gx.py +500 -0
- centraal_dataframework-0.3.0/tests/test_runner.py +163 -0
- centraal_dataframework-0.3.0/tests/test_runner_extra.py +58 -0
- centraal_dataframework-0.3.0/tests/test_settings.py +42 -0
- centraal_dataframework-0.3.0/tests/test_synapse.py +468 -0
- centraal_dataframework-0.3.0/tests/test_synapse_integration.py +196 -0
- centraal_dataframework-0.3.0/tests/test_tasks.py +316 -0
- centraal_dataframework-0.3.0/tests/test_utils.py +45 -0
- centraal_dataframework-0.1.2/PKG-INFO +0 -212
- centraal_dataframework-0.1.2/README.md +0 -164
- centraal_dataframework-0.1.2/centraal_dataframework/__init__.py +0 -13
- centraal_dataframework-0.1.2/centraal_dataframework/blueprints.py +0 -74
- centraal_dataframework-0.1.2/centraal_dataframework/email_sender.py +0 -59
- centraal_dataframework-0.1.2/centraal_dataframework/excepciones.py +0 -57
- centraal_dataframework-0.1.2/centraal_dataframework/resources.py +0 -148
- centraal_dataframework-0.1.2/centraal_dataframework/runner.py +0 -139
- centraal_dataframework-0.1.2/centraal_dataframework/tasks.py +0 -46
- centraal_dataframework-0.1.2/centraal_dataframework/utils.py +0 -18
- centraal_dataframework-0.1.2/pyproject.toml +0 -110
- centraal_dataframework-0.1.2/tests/test_runner.py +0 -28
- centraal_dataframework-0.1.2/tests/test_utils.py +0 -10
- {centraal_dataframework-0.1.2 → centraal_dataframework-0.3.0}/LICENSE +0 -0
- {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
|
+
|