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
type |
Reads | Key fields |
|---|---|---|
landing |
Files in a landing directory or object store (CSV, JSON, JSONL, Parquet) | path, format, pattern |
stream |
A file or table location read in micro-batches by watermark | path, format, watermark_field |
table |
An upstream lakehouse table (catalog name or table: reference) |
path, format |
delta |
A Delta table directory | path |
iceberg |
An Iceberg table directory | path |
database |
A relational database, by query | query, options, watermark_field |
dlt |
An HTTP API or dlt verified source | dlt |
sftp |
Files on an SFTP server | path, pattern, format, options |
Files / landing zone
Read files from a landing directory — the classic bronze source. Formats: csv, tsv, json, ndjson / jsonl,
xml, xlsx / xls / excel, fixed_width, avro, parquet; .gz and .zip files of any of them are read as well.
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. |
options |
How to read the format — see Source options. |
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 | ...
type: delta and type: iceberg read a table directory by path (path: "s3://lake/silver/orders/") without a catalog.
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 |
|---|---|
path |
The connection string, normally env:VAR so the password stays out of the contract. |
query |
The SQL to execute against the source (default: the whole dataset table). |
watermark_field |
Column used to fetch only new/changed rows (see Load modes). |
options |
fetch_size, partitioned reads, native CDC (DatabaseOptions); for a mongodb:// path, database, filter, projection, batch_size (MongoDbOptions). |
A mongodb:// or mongodb+srv:// path reads a MongoDB-protocol collection (MongoDB, Atlas, Cosmos DB's MongoDB
API, DocumentDB); dataset is the collection and each document is one row.
REST API and dlt sources
Ingest from an HTTP API or any dlt verified source (DltSourceConfig). The source kind is dlt. There are two modes, chosen by which fields you set.
Mode 1: verified source. Name a dlt verified source and resource.
source:
type: dlt
dlt:
source: stripe_analytics # dlt verified-source module
resource: charges # resource within it
credentials:
api_key: "${STRIPE_API_KEY}" # resolved from the environment at run time
Mode 2: declarative REST API. Declare the base URL and endpoints inline.
source:
type: dlt
dlt:
base_url: "https://api.example.com/v2/"
write_disposition: merge # append | replace | merge (default: replace)
max_table_nesting: 2 # default: 1
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 |
Mode 1: the dlt verified source and resource to run. |
base_url |
Mode 2: API root. |
endpoints[] |
Mode 2: one DltEndpointConfig per endpoint: name, path, params, paginator. |
credentials |
Auth material. A value written as ${ENV_VAR} is read from the environment. |
write_disposition |
append / replace / merge. |
max_table_nesting |
How deep to auto-unnest JSON responses. |
The reference framework needs the dlt extra (pip install lakelogic[dlt]).
SFTP drop folder
Fetch files from an SFTP server, then validate them like any landing file. Once downloaded they are
read by the engine's own landing reader, so every landing format and its options
apply, and incremental compares each file's server modification time with the run log's watermark.
source:
type: sftp
path: "sftp://ingest@files.example.com:22/inbound/" # user@host[:port]/remote-dir
pattern: "orders_*.csv" # glob within the remote dir (default: *)
format: csv # csv | json | parquet (default: csv)
load_mode: incremental # only files modified since the last run
options:
private_key_path: ~/.ssh/id_ed25519
known_hosts: ~/.ssh/known_hosts # omit to verify against the default known_hosts
| Field | Purpose |
|---|---|
path |
sftp://user@host[:port]/dir. Port defaults to 22. A password in the URI is rejected: it would reach logs and run metadata. |
pattern |
Glob for files in the remote directory. |
format |
The file format; its options are accepted alongside the SFTP ones. |
load_mode |
incremental downloads only files whose server mtime is newer than the last run's watermark. full reads every matching file. |
options.username |
Used when the URI has no user. |
options.password / options.private_key_path |
Authentication. Use one. A password must be env:VAR — a literal is rejected. |
options.known_hosts |
Host-key file. Omitted means the default ~/.ssh/known_hosts. Only an explicit known_hosts: null turns verification off, and the reference framework warns when it does. |
options.staging_path |
Where the files are downloaded before they are read. Required on Spark (a Volume or lakehouse Files folder the cluster can read); a local temporary folder otherwise. |
Credentials can come from the environment instead of the contract:
| Variable | Replaces |
|---|---|
LAKELOGIC_SFTP_USER |
username (when neither the URI nor options has one) |
LAKELOGIC_SFTP_PASSWORD |
options.password |
LAKELOGIC_SFTP_KEY |
options.private_key_path |
Kafka and Azure Event Hubs
type: stream with options.kind reads a message broker. Every micro-batch is validated and written, and the read
position is committed after each write, so the next run continues where the last one stopped. Messages must be JSON
objects; one that is not is quarantined with the reason and the stream continues.
source:
type: stream
options:
kind: kafka # kafka | eventhubs
brokers: env:KAFKA_BROKERS # host:port[,host:port]
topic: rides
starting_offsets: earliest # earliest | latest — first run only
trigger: available_now # available_now (drain, then stop) | continuous
security_protocol: SASL_SSL # PLAINTEXT | SSL | SASL_PLAINTEXT | SASL_SSL
sasl_mechanism: PLAIN
sasl_username: my-user
sasl_password: env:KAFKA_PASSWORD # never a literal
kind: eventhubs needs only connection_string: env:VAR: the brokers and the SASL login come from it. Every key is
listed under KafkaOptions and EventHubsOptions in the generated reference.
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:
Source options
source.options holds how to read the source. Which keys are allowed depends on the source, and an unknown key is
rejected with a suggestion (header_rows → "did you mean header_row?"):
| Source | Options | Keys |
|---|---|---|
format: csv / tsv |
CsvOptions |
delimiter, encoding, skip_rows, has_header, quote_char, null_values, comment_prefix |
format: xlsx / xls / excel |
ExcelOptions |
sheet_name, header_row, skip_footer |
format: json / ndjson / jsonl |
JsonOptions |
multiline |
format: xml |
XmlOptions |
row_tag |
format: fixed_width |
FixedWidthOptions |
columns, record_length, encoding, skip_rows, skip_footer, strip |
format: avro / parquet |
AvroOptions / ParquetOptions |
— |
| every file format | archive_member, implied_decimals, date_formats, decimal_comma |
|
type: database |
DatabaseOptions / MongoDbOptions |
see Database |
type: sftp |
SftpOptions + the format's |
username, password, private_key_path, known_hosts |
type: stream + kind |
KafkaOptions / EventHubsOptions |
see Kafka and Azure Event Hubs |
Every option has its meaning, default and an example in the generated reference (skills/olc-reference.md).
- Settings live only under
options.source.record_length(orencoding,skip_rows,skip_footer,strip) is rejected with "record_lengthbelongs undersource.options". - Credentials are never written in a contract. A key that holds a secret (
password,*_token,*_secret,connection_string, …) must be an environment reference,env:VARor${ENV:VAR}. engine_optionsis the one free-form corner: engine-specific extras, passed through unchecked.
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.