Ingestion

dlt: das EL vor dem T.

Die Bibliothek, die Rohdaten aus APIs und Dateien nach Postgres raw lädt – mit automatischer Schema-Inferenz, Normalisierung und Incremental. Warum wir keine eigenen Ingestion-Skripte mehr schreiben, was dlt ausmacht und wie ein Lauf in drei Phasen abläuft.

01

Warum dlt

statt eigener Python-Skripte

Der naheliegende Weg zur Ingestion ist ein Skript: requests holt das JSON, pandas formt es, to_sql schreibt es. Das läuft – bis die Quelle ein Feld ändert, der Load halb durchbricht, oder man dasselbe für die nächste Quelle nochmal baut. Genau daraus entstand dlt: Der Gründer, jahrelang Freelancer, baute immer wieder dieselben Skripte neu und fragte sich, ob es nicht wiederverwendbaren Code dafür geben sollte.

requests + pandas + to_sql

Ein Skript pro Quelle. Bricht, wenn ein Feld dazukommt. Kein Incremental, kein Retry, kein sauberes Normalisieren von nested JSON – alles Handarbeit.

dlt

Deklarativ: pipeline.run(source). Schema-Inferenz & -Evolution, Normalisierung, Incremental und idempotenter Load sind eingebaut.

Schema-Evolution

Ändert sich die Quelle, bricht nichts

Ein neues Feld? dlt nimmt die Spalte automatisch auf. Typ ändert sich? Es entsteht eine Variant-Spalte – statt eines gebrochenen Skripts.

Normalisierung

Nested JSON → relationale Tabellen

Verschachtelte Objekte werden zu Spalten, Listen zu Child-Tabellen – verknüpft über _dlt_id und _dlt_parent_id.

Incremental

Nur Neues, deklarativ

Ein Cursor (z. B. updated_at) lädt nur geänderte Records; append, replace oder merge per Write-Disposition.

Robust & portabel

Kein Backend, läuft überall

Rerun ist sicher (idempotenter Load-Package). Kein Service, keine Container – pip-installierbar, lokal auf DuckDB testbar, in Prod nach Postgres.

Ehrlich · Grenzen

dlt ist EL, nicht T – die Transformation macht dbt. Für Standard-SaaS-Quellen haben Fivetran/Airbyte mehr fertige Konnektoren „out of the box“. Und: Du schreibst Python – kein Klick-UI. Für einen code-first, portablen Stack ist genau das der Vorteil. Aktuell: dlt 1.x · REST-API-Source, Schema-Contracts, 100+ Destinations.

02

Was ist dlt

Building Blocks · Phasen

Eine Open-Source-Python-Bibliothek, die Daten aus oft chaotischen Quellen in saubere Datasets lädt – das E und L von ELT (dbt ist das T). Eine Pipeline verbindet Sources mit einem Destination; intern läuft jeder Lauf in drei Phasen: Extract → Normalize → Load.

Building Blocks

Dein Code

PipelineVerbindet Source und Destination – `pipeline.run()`.SourceSammlung von Resources – z. B. eine API.ResourceGenerator-Funktion, die Records yielded – eine Tabelle.DestinationZiel: Postgres, DuckDB, BigQuery … austauschbar.Write Dispositionappend · replace · merge (Upsert).IncrementalCursor (z. B. updated_at) – nur Neues laden.SchemaAus den Daten abgeleitet; evolviert automatisch.StateAm Destination persistiert; trägt Cursor über Läufe.
Phasen & Artefakte

Was dlt intern tut

ExtractRecords aus der Quelle → Load Package auf Disk.NormalizeSchema ableiten, nested JSON → Tabellen, Metadaten.LoadAtomar ins Destination; Schema-Migration; idempotent.Load PackageVersioniertes Paket mit eindeutiger ID – Rerun-sicher._dlt_*Metadaten: _dlt_id, _dlt_load_id, _dlt_parent_id.Secrets / Config.dlt/secrets.toml, .dlt/config.toml, env vars.

Merksatz: Du beschreibst die Resource – dlt kümmert sich um Schema, Normalisierung und einen sicheren Load.

03

Wie ein Lauf abläuft

läuft · Endlosschleife

pipeline.run(finnhub_source) läuft in drei Phasen: Extract holt die Records → Normalize leitet das Schema ab und flacht nested JSON ab → Load schreibt atomar nach Postgres raw. Und wenn sich die Quelle ändert, evolviert das Schema automatisch mit.

Der LaufExtract → Normalize → Loadpipeline.run()
SourceFinnhubREST · Resource
Phase 1Extract→ load package
Phase 2Normalizeschema + flatten
Phase 3Load→ raw (atomar)
Source-Record (JSON)roh
{
  "symbol": "AAPL",
  "price": 189.40,
  "ts": 1719421200,
  "meta": {
    "exchange": "NASDAQ",
    "currency": "USD"
  }
}
raw.quotes · abgeleitetes Schema0 Spalten
symboltext
pricedouble
tsbigint
meta__exchangetext
meta__currencytext
_dlt_idtext
_dlt_load_idtext
HighlightSchema-Evolution – die Quelle ändert sichnächster Lauf
Source-Record · jetzt mit neuem Feldgeändert
{
  "symbol": "AAPL",
  "price": 191.10,
  "ts": 1719507600,
  "meta": { "exchange": "NASDAQ", "currency": "USD" },
  "sector": "Technology"   → neues Feld
}
raw.quotes · Schema evolviert7 Spalten
symboltext
pricedouble
tsbigint
meta__exchangetext
meta__currencytext
_dlt_idtext
_dlt_load_idtext
sector+ neutext
automatisch · kein Bruch
dlt erkennt das neue Feld und fügt die Spalte sector selbst hinzu – ALTER TABLE, ohne gebrochene Pipeline. Ein Skript hätte hier entweder abgebrochen oder das Feld stillschweigend verworfen.
lokal → prod: gleiche Pipeline, DuckDB zum Testen, Postgres in Produktion – nur das Destination wechselt.rerun-sicher: bricht ein Load ab, wird das Load-Package erneut geladen – idempotent.

Das Schema folgt den Daten – nicht umgekehrt.

— Schema-Inferenz & Evolution