data_drain 0.4.0 → 0.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff8a69a33cb9dc44d9252792b7b6531707ba11f330c81cc9c27f4613e74ef0be
4
- data.tar.gz: c6503db21d32c3ea60fe2be121d6422fb5305bf222b07f200cc81364e7b9c152
3
+ metadata.gz: 036367c9d2512c4a9a2785bbef6efeaaf9ee013ee31c013e1527001f91f22d65
4
+ data.tar.gz: c7dc050b51c3024ad9e2e62ae57e28bb549fee9a0341aea5ee524d4b55a07da5
5
5
  SHA512:
6
- metadata.gz: b916b2ee021d9cf6060ae00b2c5f924811b3f42ef7d475b329960be4b80035e1a3348dfb28da49d0a8fc8ec5e6ec749d9145da643f95f18f952b8be1e4c45bde
7
- data.tar.gz: 39c9d09e004e75a84f135651f12d7b0eec810f39083fd565ac7edeca3affc83a31f08db4b187312b85121e9284ddb50a4b3f6e4c2cd8d6fb46ff0e7e5888af3a
6
+ metadata.gz: 9ed7fb071a8d1f0c103c44c402279b9b575b535944a21db6f260b5d2aa4da2675f56095a9d79d1054c8e4c48ac578a955673befde82a84ca030ee508adf11e8e
7
+ data.tar.gz: f4c3665f97d5f4c287f70a715bd4fc36264bae977f868bb5f062bade177527cb6bfb10dbaf6c83fb6280ab2fce4787bf8abc1789fa9fb5893ac99f3a0a2b1ee0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.1] - 2026-04-15
4
+
5
+ ### Docs
6
+
7
+ - `skill/references/eventos-telemetria.md`: nuevos eventos `script_uploaded` y `script_upload_error`.
8
+ - `README.md`: ejemplos de `script_path` en GlueRunner y observabilidad.
9
+
10
+ ## [0.5.0] - 2026-04-15
11
+
12
+ ### Features
13
+
14
+ - `Storage::S3#upload_file` y `Storage::Local#upload_file`: primitiva para subir archivos al storage configurado. (item 37)
15
+ - `GlueRunner.upload_script(local_path:, bucket:, folder:, filename:)`: sube script Python local a S3 usando el `Storage::S3` adapter existente. Emite `glue_runner.script_uploaded` (INFO) y `glue_runner.script_upload_error` (ERROR). (item 37)
16
+ - `GlueRunner.create_job` y `GlueRunner.ensure_job` aceptan `script_path:` + `script_bucket:` + `script_folder:` + `script_filename:` para subir scripts locales automáticamente. Si se usa `script_location:`, comportamiento idéntico al anterior. (item 37)
17
+
18
+ ### Docs
19
+
20
+ - `docs/glue-jobs-lifecycle.md`: sección "Subir scripts locales" con patrón completo, permisos IAM mínimos y limitación de concurrencia.
21
+ - `docs/glue_pyspark_example.py`: ejemplo de uso con `script_path`.
22
+
23
+ ### Notas
24
+
25
+ - **Upload NO es idempotente en sentido estricto:** `put_object` sobrescribe siempre. Documentado.
26
+ - `upload_script` requiere `storage_mode = :s3`. En `:local` levanta `ConfigurationError`.
27
+
3
28
  ## [0.4.0] - 2026-04-15
4
29
 
5
30
  ### Features
data/README.md CHANGED
@@ -115,11 +115,13 @@ DataDrain::GlueRunner.job_exists?("my-glue-export-job")
115
115
  job = DataDrain::GlueRunner.get_job("my-glue-export-job")
116
116
  # => Aws::Glue::Types::Job (Name, Command, DefaultArguments, etc.)
117
117
 
118
- # Crear un job
118
+ # Crear un job con script local (v0.5.0+)
119
119
  job = DataDrain::GlueRunner.create_job(
120
120
  "my-glue-export-job",
121
121
  role_arn: "arn:aws:iam::123:role/GlueServiceRole",
122
- script_location: "s3://my-bucket/scripts/export.py",
122
+ script_path: "scripts/glue/export.py", # local → S3 automático
123
+ script_bucket: "my-bucket",
124
+ script_folder: "scripts",
123
125
  default_arguments: { "--extra-files" => "s3://my-bucket/scripts/udf.py" },
124
126
  timeout: 1440,
125
127
  max_retries: 2
@@ -129,8 +131,9 @@ job = DataDrain::GlueRunner.create_job(
129
131
  job = DataDrain::GlueRunner.ensure_job(
130
132
  "my-glue-export-job",
131
133
  role_arn: "arn:aws:iam::123:role/GlueServiceRole",
132
- script_location: "s3://my-bucket/scripts/export.py",
133
- timeout: 1440
134
+ script_path: "scripts/glue/export.py",
135
+ script_bucket: "my-bucket",
136
+ script_folder: "scripts"
134
137
  )
135
138
 
136
139
  # Eliminar un job
@@ -198,6 +201,7 @@ ArchivedVersion.destroy_all(year: 2024, month: 3) # un mes globalmente
198
201
  ```
199
202
  component=data_drain event=engine.complete table=versions duration_s=12.4 export_duration_s=8.1 purge_duration_s=3.9 count=150000
200
203
  component=data_drain event=engine.purge_heartbeat table=versions batches_processed_count=100 rows_deleted_count=500000
204
+ component=data_drain event=glue_runner.script_uploaded local_path=scripts/glue/export.py s3_path=s3://my-bucket/scripts/export.py bytes=4521
201
205
  component=data_drain event=glue_runner.failed job=my-export-job run_id=jr_abc123 status=FAILED duration_s=301.0
202
206
  ```
203
207
 
@@ -0,0 +1,167 @@
1
+ # Observaciones — v0.5.0
2
+
3
+ Generado por: big-pickle
4
+ Fecha: 2026-04-15
5
+ Revisor: big-pickle (análisis de código real vs plan v2)
6
+
7
+ ---
8
+
9
+ ## Observación 1 — Breaking change implícito en `create_job`/`ensure_job`
10
+
11
+ **Severidad:** Alta
12
+
13
+ **Hallazgo:** El plan dice "Breaking: ninguno", pero la firma cambia:
14
+
15
+ ```ruby
16
+ # ACTUAL (script_location: es keyword REQUIRED)
17
+ def self.create_job(job_name, role_arn:, script_location:, ...)
18
+
19
+ # PLAN (script_location: nil con default, role_arn: sigue required → SyntaxError)
20
+ def self.create_job(job_name, role_arn:, script_location: nil, script_path: nil, ...)
21
+ # required keyword argument cannot have a default value
22
+ ```
23
+
24
+ **Fix requerido:** `role_arn:` también debe tener default `nil`. Esto es técnicamente un cambio de API (aunque todos los callers usan kwargs, así que no rompe en la práctica).
25
+
26
+ **Estado:** Verificar en pre-flight checklist.
27
+
28
+ ---
29
+
30
+ ## Observación 2 — `s3_client` existe y usa credential chain
31
+
32
+ **Severidad:** Confirmada OK
33
+
34
+ **Verificado en** `lib/data_drain/storage/s3.rb:44-50`:
35
+
36
+ ```ruby
37
+ def s3_client
38
+ Aws::S3::Client.new(
39
+ region: @config.aws_region,
40
+ access_key_id: @config.aws_access_key_id,
41
+ secret_access_key: @config.aws_secret_access_key
42
+ )
43
+ end
44
+ ```
45
+
46
+ El SDK acepta `nil` en las credenciales y resuelve via credential chain automáticamente. Plan OK.
47
+
48
+ ---
49
+
50
+ ## Observación 3 — `changed_fields` incluye comparación de `script_location`
51
+
52
+ **Severidad:** Confirmada OK
53
+
54
+ **Verificado en** `lib/data_drain/glue_runner.rb:174-175`:
55
+
56
+ ```ruby
57
+ changed << :command if current.command.name != desired[:command_name] ||
58
+ current.command.script_location != desired[:script_location]
59
+ ```
60
+
61
+ El `script_location` se compara dentro de `:command`. Cuando `ensure_job` construya `desired[:script_location] = final_script` (resultado del upload), el diffing funcionará correctamente.
62
+
63
+ ---
64
+
65
+ ## Observación 4 — `Storage::Local#upload_file` requiere directorio manual
66
+
67
+ **Severidad:** Media
68
+
69
+ **Hallazgo:** No existe `Configuration#local_bucket`. El `bucket` en `Storage::Local#upload_file` es un directorio local pasado por el caller.
70
+
71
+ **Plan OK:** el plan ya documenta esto en la firma del método. Confirmar en pre-flight checklist que el implementador lo sepa.
72
+
73
+ ---
74
+
75
+ ## Observación 5 — `resolve_script_location` es `private_class_method` pero llamado desde métodos públicos
76
+
77
+ **Severidad:** Info
78
+
79
+ **Hallazgo:** Ruby permite que métodos públicos de clase llamen `private_class_method`. No hay error. El plan está correcto. Solo documentar para claridad del implementador.
80
+
81
+ ---
82
+
83
+ ## Observación 6 — `spec/data_drain/storage/local_spec.rb` no existe
84
+
85
+ **Severidad:** Media
86
+
87
+ **Hallazgo:** El plan propone specs para `Storage::Local#upload_file` pero el archivo no existe. Hay que crearlo en la fase correspondiente.
88
+
89
+ **Acción:** Crear `spec/data_drain/storage/local_spec.rb` con los specs del plan.
90
+
91
+ ---
92
+
93
+ ## Observación 7 — `spec/data_drain/glue_runner_spec.rb` tiene specs existentes de `create_job`/`ensure_job`
94
+
95
+ **Severidad:** Info
96
+
97
+ **Hallazgo:** Ya existen specs en `spec/data_drain/glue_runner_spec.rb:161+` para `create_job` y `ensure_job`. Los specs nuevos del plan deben ser adicionales a los existentes, no reemplazarlos. Verificar que los tests existentes sigan pasando.
98
+
99
+ ---
100
+
101
+ ## Observación 8 — Test de `storage_mode = :local` requiere `reset_adapter!`
102
+
103
+ **Severidad:** Baja
104
+
105
+ **Hallazgo:** El test "levanta ConfigurationError si storage_mode es :local" cambia `storage_mode` globalmente. Debe hacer `reset_adapter!` en el `after` block para no contaminar otros tests.
106
+
107
+ **Plan OK:** el plan menciona esto, solo confirmar que se implemente correctamente.
108
+
109
+ ---
110
+
111
+ ## Observación 9 — Campo `bytes` en evento observabilidad
112
+
113
+ **Severidad:** Baja
114
+
115
+ **Hallazgo:** El plan usa `bytes` como nombre de campo en `glue_runner.script_uploaded`. Según Wispro-Observability-Spec v1, los contadores deben usar la palabra `count` en la key.
116
+
117
+ **Alternativas:**
118
+ - `byte_count` (más consistente con spec)
119
+ - `file_size` (más semántico)
120
+
121
+ **Decisión:** Mantener `bytes` por convención established en contexto AWS/S3 (donde `ContentLength` se reporta como `bytes`). Bajo riesgo.
122
+
123
+ ---
124
+
125
+ ## Observación 10 — `content_type: "text/x-python"` es explícito
126
+
127
+ **Severidad:** Info
128
+
129
+ **Verificado:** S3 infiere correctamente `text/x-python` para archivos `.py` si no se provee. El plan usa explícito para mayor predictibilidad. OK.
130
+
131
+ ---
132
+
133
+ ## Observación 11 — Concurrencia sin lock
134
+
135
+ **Severidad:** Info
136
+
137
+ **Documentado en** plan. `put_object` sobrescribe siempre. Scripts PySpark son pequeños, riesgo bajo. Items 38 y 39 definidos como follow-ups.
138
+
139
+ ---
140
+
141
+ ## Resumen para el implementador
142
+
143
+ | # | Severidad | Acción |
144
+ |---|-----------|--------|
145
+ | 1 | Alta | Clarificar en plan que `role_arn:` también tiene default `nil` |
146
+ | 2 | Confirmada OK | `s3_client` existe, credential chain OK |
147
+ | 3 | Confirmada OK | `changed_fields` compara `script_location` |
148
+ | 4 | Media | Documentar que Local requiere directorio destino manual |
149
+ | 5 | Info | `private_class_method` permitido en Ruby de clase pública |
150
+ | 6 | Media | Crear `spec/data_drain/storage/local_spec.rb` |
151
+ | 7 | Info | Tests existentes de `create_job`/`ensure_job` no se tocan |
152
+ | 8 | Baja | Test de `storage_mode = :local` con `reset_adapter!` |
153
+ | 9 | Baja | Mantener `bytes` (convención AWS) |
154
+ | 10 | Info | `content_type` explícito OK |
155
+ | 11 | Info | Concurrencia documentada, items 38/39 follow-ups |
156
+
157
+ ---
158
+
159
+ ## Checklist de pre-implementación
160
+
161
+ - [ ] Clarificar que `role_arn:` también tiene default `nil` en el plan
162
+ - [ ] Verificar `s3_client` existe en `Storage::S3` ✅
163
+ - [ ] Verificar `changed_fields` incluye `script_location` ✅
164
+ - [ ] Confirmar que no hay `Configuration#local_bucket` existente
165
+ - [ ] Crear `spec/data_drain/storage/local_spec.rb`
166
+ - [ ] Ejecutar `bundle exec rspec` antes de empezar para tener baseline verde
167
+ - [ ] Ejecutar `bundle exec rubocop` antes de empezar para tener baseline verde