Ingestion & Sources
Everything about where the data comes from lives under the source block (the SourceConfig model — 25 fields). One source describes what to read, how to read it incrementally, how to partition it, how to handle change-data-capture, and what to do with the input after it's ingested.
type selects the kind of source; the remaining fields are the knobs that kind uses. The sections below group them by kind, then by cross-cutting concern (load modes, partitioning, CDC, post-ingestion).
Powered by
The source block is a Pydantic model, so every field here is type-validated on load. The reference framework reads each kind with the appropriate library: file/object-store reads via polars / pyarrow (and DuckDB's httpfs / azure extensions, or s3fs / gcsfs / adlfs for cloud paths); databases via their DB-API/JDBC driver; REST APIs via dlt; streaming micro-batches via PySpark structured streaming (or the engine's incremental reader).
Source kinds
Files / landing zone
Read files from a landing directory — the classic bronze source. Formats: CSV, JSON, JSONL, Parquet.
source:
type: landing
path: "s3://bucket/landing/orders/" # or a local path, abfss://, gs://
format: csv # csv | json | jsonl | parquet
pattern: "*.csv" # glob to select files
flatten_nested: true # explode nested JSON into columns (or a list of paths)
empty_behavior: skip # skip | fail — when no files match
| Field | Purpose |
|---|---|
path |
Root location of the input files. |
format |
File format to parse. |
pattern |
Glob for selecting files within path. |
flatten_nested |
true to flatten nested JSON, or a list of specific nested paths to flatten. |
manifest_path |
Read an explicit manifest of files instead of globbing. |
empty_behavior |
skip (no-op on empty input) or fail. |
Upstream lakehouse table
Read another governed table (the normal silver/gold source — a link in the mesh).
source:
type: table
path: "table:catalog.schema.silver_orders" # an upstream table reference
format: delta # delta | iceberg | ducklake | ...
Database / SQL query
Pull from a relational source by query.
source:
type: database
query: "SELECT * FROM public.orders WHERE updated_at > :watermark"
load_mode: incremental
watermark_field: updated_at
| Field | Purpose |
|---|---|
query |
The SQL to execute against the source. |
watermark_field |
Column used to fetch only new/changed rows (see Load modes). |
REST API (via dlt)
Ingest from an HTTP API using an embedded dlt source — pagination, auth, and multiple endpoints declared inline (DltSourceConfig).
source:
type: api
dlt:
source: rest_api
base_url: "https://api.example.com/v2/"
write_disposition: merge # append | replace | merge
max_table_nesting: 2
credentials: { token: "${API_TOKEN}" } # from env, never inline in the repo
endpoints:
- name: orders
path: "/orders"
params: { status: "all" }
paginator: cursor # how the API pages results
DltSourceConfig |
Purpose |
|---|---|
source / resource |
The dlt source/resource to run. |
base_url |
API root. |
endpoints[] |
One DltEndpointConfig per endpoint: name, path, params, paginator. |
credentials |
Auth material (reference env vars — don't hard-code secrets). |
write_disposition |
append / replace / merge. |
max_table_nesting |
How deep to auto-unnest JSON responses. |
Streaming / micro-batch
A streaming source is a table/file source read in micro-batches driven by a watermark. The contract stays the same; the framework reads incrementally each trigger. Combine load_mode: incremental with a watermark_field and (optionally) a streaming engine on the framework side.
source:
type: stream
path: "s3://bucket/events/"
format: json
load_mode: incremental
watermark_field: event_time
watermark_strategy: append # how the watermark advances per batch
The same governance (schema, quality, quarantine, PII, lineage) applies to every micro-batch. See the reference framework's
StreamSinkfor the native structured-streaming path.
Load modes
load_mode controls how much of the source is read each run.
source:
load_mode: incremental # full | incremental
watermark_field: updated_at
watermark_strategy: append
watermark_date_parts: [year, month, day] # for date-partitioned watermarks
lookback: "3d" # re-read a trailing window to catch late data
from_date: "2026-01-01" # explicit bounds (backfill / bounded reprocess)
to_date: "2026-01-31"
| Field | Purpose |
|---|---|
load_mode |
full (read everything) or incremental (only new/changed). |
watermark_field |
The column the watermark tracks. |
watermark_strategy |
How the watermark advances (e.g. append-only vs upsert semantics). |
watermark_date_parts |
For date-part watermarks — the parts that form the boundary. |
lookback |
A trailing window re-read every run to capture late-arriving data. |
from_date / to_date |
Explicit bounds for backfills or bounded reprocessing. |
pipeline_log_table / pipeline_name |
Where the watermark/run state is persisted. |
Incremental needs a persisted watermark
Incremental mode reads the last watermark from run state. Runners that read the whole input each time (e.g. a demo that reloads a landing zone) set LAKELOGIC_SKIP_INCREMENTAL_CHECK=1 to bypass the watermark requirement.
Change data capture (CDC)
When the source emits change events (insert/update/delete), OLC applies them as a CDC stream rather than a plain append.
source:
type: database
load_mode: incremental
cdc_op_field: op # column holding the operation
cdc_delete_values: ["D", "delete"] # values that mean "delete this row"
cdc_timestamp_field: op_ts # order events by this to resolve last-writer-wins
| Field | Purpose |
|---|---|
cdc_op_field |
The column that carries the change operation. |
cdc_delete_values |
Which values in that column represent a delete. |
cdc_timestamp_field |
Orders change events so the latest wins on merge. |
Deletes surfaced by CDC pair naturally with soft-delete materialization so history is preserved rather than physically removed.
Partitioned reads
For date-partitioned inputs, SourcePartition selects which partitions to read.
source:
partition:
format: "year=%Y/month=%m/day=%d" # partition path layout
lookback_days: 3
start_date: "2026-01-01"
end_date: "2026-01-31"
file_pattern: "*.parquet"
partition_filters: { region: "eu" } # additional predicate pushdown
After ingestion: clean up the source
Once input is safely ingested, post_ingestion decides what happens to it — including deleting or archiving the consumed files. This is the "delete files once ingested" behavior; it has its own page because it's a lifecycle concern shared with the server block.
source:
post_ingestion:
action: delete # delete | archive | retain
cleanup_is_blocking: false
archive_path: "s3://bucket/archive/orders/" # required when action: archive
→ Full detail in Post-Ingestion Lifecycle.
Resilience
Transient source failures are retried per RetryConfig:
Every source field at a glance
type, query, path, format, load_mode, pattern, watermark_field, cdc_op_field, cdc_delete_values, cdc_timestamp_field, dlt, partition, empty_behavior, watermark_strategy, target_path, lookback, from_date, to_date, pipeline_log_table, pipeline_name, manifest_path, watermark_date_parts, partition_filters, flatten_nested, post_ingestion.