datapipeline 3.0.0: transform, Parallelmodus, chained und String-Bedingungen entfernt
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# DataPipeline – ETL-Framework in Ruby
|
||||
|
||||
Stand: 2026-10-09 · Gem `datapipeline` 2.1.0 ([CHANGELOG.md](CHANGELOG.md)) · Ruby 3.4
|
||||
Stand: 2026-10-09 · Gem `datapipeline` 3.0.0 ([CHANGELOG.md](CHANGELOG.md)) · Ruby 3.4
|
||||
|
||||
`DataPipeline` liest Daten aus einer Quelle, schickt jede Zeile durch eine Kette von Schritten (prüfen, filtern,
|
||||
umbauen, anreichern, abgleichen) und schreibt das Ergebnis in ein oder mehrere Ziele. Eine Pipeline ist ein kurzes
|
||||
@@ -15,7 +15,7 @@ Festbreiten-Dateien und REST-APIs.
|
||||
4. [Plugins](#4-plugins) – mitgelieferte Quellen und Ziele, eigene Plugins, Abhängigkeiten
|
||||
5. [Satzarten-Layouts](#5-satzarten-layouts) – Dateien mit Kopf-, Positions- und Schlusssätzen
|
||||
6. [Betrieb](#6-betrieb) – Kommandozeile, Verbindungen, Sperre, Abgleich, Historie, Benachrichtigung, Fehlersuche
|
||||
7. [Veraltetes und Umstieg](#7-veraltetes-und-umstieg) – von 2.0 auf 2.1, `transform` und Parallelmodus
|
||||
7. [Umstieg](#7-umstieg) – von 2.x auf 3.0 (`transform` entfernt), von 2.0 auf 2.1
|
||||
8. [Entwicklung](#8-entwicklung) – Aufbau, Tests, Gem bauen
|
||||
9. [Ausblick](#9-ausblick)
|
||||
|
||||
@@ -783,7 +783,6 @@ Tippfehler fallen also sofort auf.
|
||||
| --- | --- |
|
||||
| `streamed(flag = true)` | Zeile für Zeile statt Batch (3.3) |
|
||||
| `verbose(flag = true)` | Quelle, Ergebnis und Ziel im Batch als JSON auf `$stdout` |
|
||||
| `chained(flag = true)` | nur für `transform` nötig (7.2) |
|
||||
| `configure(opts)` | mehrere Einstellungen als Hash, z. B. `configure(streamed: true, version: '1.0')` |
|
||||
| `run` | führt die Pipeline aus und liefert `DataPipeline::Stats` |
|
||||
| `DataPipeline.new(**settings) { … }` | Einstellungen nur für diese Pipeline, z. B. `new(limit: 10)` |
|
||||
@@ -918,8 +917,8 @@ Dry-Run wird die Fehlerdatei nicht geschrieben.
|
||||
| `to_s` | `read=9 written=4 rejected=4 0.097s`; mit Warnungen `warnings=n`, mit `totals` `spalte=quelle/ergebnis` |
|
||||
| `to_h` | alles als Hash, wie in der Laufhistorie |
|
||||
|
||||
Der Logger schreibt `start` und `finish` (Info), jede verworfene Zeile und Warnung (Warnung), veraltete Aufrufe
|
||||
einmal je Prozess (Warnung, 7.2) und einen Abbruch (Fehler). `on_finish` läuft nach jedem Lauf, auch nach einem
|
||||
Der Logger schreibt `start` und `finish` (Info), jede verworfene Zeile und Warnung (Warnung) und einen
|
||||
Abbruch (Fehler). `on_finish` läuft nach jedem Lauf, auch nach einem
|
||||
Abbruch; ein Fehler im Hook wird nur geloggt und ändert weder Ergebnis noch Exit-Code.
|
||||
|
||||
```ruby
|
||||
@@ -1507,53 +1506,56 @@ gelesen 1250 · geschrieben 1243 · verworfen 7 · Warnungen 0
|
||||
| `LayoutError: TEXT: "…" longer than 30` | Wert passt nicht in das Feld einer Festbreiten-Datei | vorher `validate LAYOUT` mit `on_error`, oder den Wert kürzen |
|
||||
| `Encoding::UndefinedConversionError: U+20AC from UTF-8 to ISO-8859-1` | Zeichen gibt es im Zielzeichensatz nicht | `Windows-1252` statt ISO-8859-1, oder `replace: '?'` |
|
||||
| `connection :x not in config/connections.yml (a, b)` | Name falsch oder Datei nicht gefunden | Namen prüfen; Pfad relativ zum Arbeitsverzeichnis, sonst `connections_file` setzen |
|
||||
| `streamed: several transformations need chained (or map/filter)` | mehrere `transform` im Streaming | `map` verwenden (7.2) |
|
||||
| `WARN deprecated: …` | veralteter Aufruf | umstellen, siehe 7.2 |
|
||||
| `NoMethodError: undefined method 'transform'` (oder `chained`) | Skript für 2.x | auf `map`, `header`, `trailer` umstellen (7.1) |
|
||||
| `map: condition must be a lambda ->(row) { ... }` | `condition:` als String aus 2.x | `condition: ->(row) { … }` (7.1) |
|
||||
|
||||
Mehr sehen: `--verbose` (Log-Level Debug), `verbose` in der Pipeline (Daten als JSON), `--dry-run --limit=20` für einen
|
||||
schnellen Blick auf das Ergebnis.
|
||||
|
||||
---
|
||||
|
||||
## 7. Veraltetes und Umstieg
|
||||
## 7. Umstieg
|
||||
|
||||
### 7.1 Von 2.0 auf 2.1
|
||||
### 7.1 Von 2.x auf 3.0
|
||||
|
||||
2.1 ist abwärtskompatibel; bestehende Skripte laufen ohne Änderung. Anders als bisher:
|
||||
3.0 entfernt das alte Ausführungsmodell: `transform`, den Parallelmodus, `chained` und `condition:` als String
|
||||
(`eval`). Alle Schritte laufen jetzt verkettet, jeder bekommt die Ausgabe des vorigen. Alles andere ist unverändert;
|
||||
ein Skript, das unter 2.1 ohne `WARN deprecated` lief, läuft auch unter 3.0.
|
||||
|
||||
| Thema | 2.0 | 2.1 |
|
||||
| 2.x | 3.0 |
|
||||
| --- | --- |
|
||||
| `transform { \|row, index\| … }` | `map { \|row, ctx\| … }`, Index als `ctx.index` |
|
||||
| `transform(processing: :prepend) { \|_, _\| kopf }` | `header { \|ctx\| kopf }` |
|
||||
| `transform(processing: :append) { \|_, n\| schluss }` | `trailer { \|ctx\| schluss }`, Anzahl als `ctx.written` |
|
||||
| `transform(condition: "row['a'] == 1") { … }` | `map(condition: ->(row) { row['a'] == 1 }) { … }` |
|
||||
| `chained`, `configure(chained: true)` | weglassen |
|
||||
|
||||
Worauf beim Umstellen zu achten ist:
|
||||
|
||||
- **Parallelmodus:** Ohne `map`, `filter`, `validate` und `chained` bekam unter 2.x jeder `transform` eine eigene
|
||||
Kopie der Quellzeilen, und die Ergebnisse wurden hintereinander angehängt. Zwei `transform` auf 2 Quellzeilen
|
||||
ergaben 4 Zeilen, zwei `map` ergeben 2 Zeilen mit beiden Änderungen. Wer wirklich jede Zeile mehrfach ausgeben will,
|
||||
gibt in einem `map` ein Array zurück: `map { |r| [mit_b(r), mit_c(r)] }`.
|
||||
- **Kopfsatz:** `transform(processing: :prepend)` lief vor den Daten, `header` läuft im Batch erst danach (steht aber
|
||||
vorne). Das zählt nur, wenn der Block etwas anderes tut, als eine Zeile zu liefern.
|
||||
- **Schlusssatz:** `processing: :append` bekam im Parallelmodus die Anzahl der Quellzeilen; `ctx.written` ist die
|
||||
Anzahl der Datenzeilen nach allen Schritten, `ctx.read` die der Quellzeilen.
|
||||
- **Bedingung:** Ein String in `condition:` bricht schon beim Aufbau der Pipeline mit `ArgumentError` ab.
|
||||
|
||||
Prüfen lässt sich die Umstellung mit `etl run skript.rb --dry-run` vorher und nachher: Die Zusammenfassung und die
|
||||
ersten Zeilen sollten gleich sein.
|
||||
|
||||
### 7.2 Von 2.0 auf 2.1
|
||||
|
||||
2.1 war abwärtskompatibel. Anders als in 2.0:
|
||||
|
||||
| Thema | 2.0 | ab 2.1 |
|
||||
| --- | --- | --- |
|
||||
| Postgres, `write_row` im Streaming | jede Zeile sofort, ohne Transaktion | je 1000 Zeilen in einer Transaktion; der Rest in `finalize`/`close`, auch nach einem Abbruch |
|
||||
| CSV lesen mit UTF-8-BOM | BOM steht im ersten Spaltennamen | BOM wird übersprungen |
|
||||
| `Stats#to_s` | – | mit `totals` zusätzlich `spalte=quelle/ergebnis` |
|
||||
| `transform`, Parallelmodus, `condition:` als String | ohne Hinweis | `WARN deprecated: …` einmal je Prozess |
|
||||
| `etl` ohne `name` | – | `stats.name` = Skriptname |
|
||||
|
||||
### 7.2 `transform` und Parallelmodus
|
||||
|
||||
`transform`, der Parallelmodus und `condition:` als String (`eval`) sind veraltet und entfallen in 3.0. Ab 3.0
|
||||
laufen alle Schritte verkettet, wie heute schon, sobald `map`, `filter` oder `validate` vorkommt.
|
||||
|
||||
| Alt | Neu |
|
||||
| --- | --- |
|
||||
| `transform { \|row, index\| … }` | `map { \|row, ctx\| … }`, Index als `ctx.index` |
|
||||
| `transform(processing: :prepend) { \|_, _\| kopf }` | `header { \|ctx\| kopf }` |
|
||||
| `transform(processing: :append) { \|_, n\| schluss }` | `trailer { \|ctx\| schluss }` mit `ctx.written` |
|
||||
| `transform(condition: "row['a'] == 1")` | `map(condition: ->(row) { row['a'] == 1 })` |
|
||||
| mehrere `transform` ohne `chained` (Parallelmodus) | mehrere `map`; wer wirklich jede Zeile mehrfach ausgeben will, gibt in einem `map` ein Array zurück |
|
||||
|
||||
Bis dahin gilt für alte Skripte:
|
||||
|
||||
- **Parallelmodus** (Default ohne `map`/`filter`/`validate` und ohne `chained`): Jeder `transform` bekommt eine eigene
|
||||
Kopie der Quellzeilen, die Ergebnisse aller Schritte werden hintereinander angehängt. Beispiel mit 2 Quellzeilen und
|
||||
`transform { |r| r.merge('b' => 1) }` + `transform { |r| r.merge('c' => 2) }`: parallel 4 Zeilen (2× mit `b`, dann
|
||||
2× mit `c`), verkettet 2 Zeilen mit `b` und `c`. Im Streaming sind mehrere `transform` nur verkettet erlaubt.
|
||||
- **`transform(processing: :prepend)`** wird mit `(nil, 0)` vor den Daten aufgerufen, **`:append`** mit `(nil, n)`
|
||||
nach den Daten (parallel n = Quellzeilen, verkettet n = Datenzeilen).
|
||||
- **`condition:` als String** wird mit `eval` ausgewertet, die Variable `row` ist sichtbar.
|
||||
|
||||
`test/core/compat_test.rb` hält die Ausgabe des alten Modells fest, damit es sich bis 3.0 nicht ändert.
|
||||
|
||||
---
|
||||
|
||||
## 8. Entwicklung
|
||||
@@ -1577,13 +1579,13 @@ Bis dahin gilt für alte Skripte:
|
||||
|
||||
### 8.2 Tests
|
||||
|
||||
`bundle exec rake test` startet die Minitest-Suite in [test/](test/), rund 205 Fälle. Einzelne Datei:
|
||||
`bundle exec rake test` startet die Minitest-Suite in [test/](test/), rund 190 Fälle. Einzelne Datei:
|
||||
`bundle exec rake test TEST=test/core/steps_test.rb`.
|
||||
|
||||
| Ordner | Inhalt |
|
||||
| --- | --- |
|
||||
| `test/plugins` | je Plugin das gemeinsame Testkit `test/support/storage_contract.rb` (Schreiben und Zurücklesen, zeilenweise, als Quelle und Ziel) plus eigene Fälle; REST gegen einen lokalen Testserver; Laden der Gems erst bei Nutzung |
|
||||
| `test/core` | DSL, Schritte und Kurzschritte, Batch und Streaming, Fehlerbehandlung, Layouts, Sperre, Verbindungen, Summen, Historie, Wasserzeichen; `compat_test.rb` hält das alte `transform`-Modell fest |
|
||||
| `test/core` | DSL, Schritte und Kurzschritte, Batch und Streaming, Fehlerbehandlung, Layouts, Sperre, Verbindungen, Summen, Historie, Wasserzeichen |
|
||||
| `test/cli` | `bin/etl` als Prozess: Parameter, Dry-Run, Sperre, Historie, Exit-Codes |
|
||||
| `test/examples` | die Beispiele aus Kapitel 2: Code in der Doku = Datei, Lauf und Ergebnis; REST gegen die Demo-API |
|
||||
|
||||
@@ -1604,7 +1606,7 @@ Ein eigenes Plugin lässt sich mit `MemoryStorage` aus `test/test_helper.rb` als
|
||||
### 8.3 Gem und Versionen
|
||||
|
||||
- Version in [lib/datapipeline/version.rb](lib/datapipeline/version.rb), Änderungen in [CHANGELOG.md](CHANGELOG.md)
|
||||
(neueste oben). Neue Funktionen erhöhen die mittlere Stelle, Breaking Changes die erste (3.0 entfernt `transform`).
|
||||
(neueste oben). Neue Funktionen erhöhen die mittlere Stelle, Breaking Changes die erste (wie 3.0, 7.1).
|
||||
- Das `Gemfile` bindet das Gem über `gemspec` ein und ergänzt die Gruppen `plugins` (`pg`, `sqlite3`, `roo`,
|
||||
`caxlsx`, `http`) und `test`; `Gemfile.lock` ist versioniert. Ohne Plugin-Gems: `bundle config set without plugins`.
|
||||
- Gem bauen: `gem build datapipeline.gemspec`.
|
||||
@@ -1625,5 +1627,4 @@ Offene Punkte, Reihenfolge = empfohlene Umsetzung:
|
||||
4. **Slack:** fertige Meldung aus den Stats (`notify slack, on: :problems`), Timeout und Wiederholung bei `429`,
|
||||
Maskierung von `&`, `<`, `>`, Incoming Webhooks.
|
||||
5. **Weitere Plugins:** SFTP-Ablage.
|
||||
6. **3.0:** `transform`, Parallelmodus und `condition:` als String entfernen.
|
||||
7. **Veröffentlichung:** eigenes Remote-Repository, Lizenz festlegen, Versionen taggen.
|
||||
6. **Veröffentlichung:** eigenes Remote-Repository, Lizenz festlegen, Versionen taggen.
|
||||
|
||||
Reference in New Issue
Block a user