dlt 1.30.0 Release Notes
Breaking Changes
-
Failed load packages no longer auto-abort (#3557 @anuunchin) —
auto_abort_on_terminal_errordefaults toFalse. Withraise_on_failed_jobs=Truethe job is now retried and you get aLoadClientJobTerminalRetryinstead ofLoadClientJobFailed, and the package stays pending.Pipeline.drop_pending_packages()and thedrop-pending-packagesCLI command are deprecated in favour ofabort_packages/abort-packages. This has no effect on ephemeral storage, which auto-aborts by wiping the working directory. Set the flag toTrueto restore the old behavior. -
Filesystem layouts that omit
{ext}now get the extension appended (#4220 @mattfaltyn) — the documented extension fallback was unreachable, so these layouts wrote extension-less files. They now writemocked-table.jsonl, and.jsonl.gzstays intact for compressed jobs. Pipelines already running an ext-less layout will start writing different file names. -
Table prefix keeps its separator (#4283 @AnnasMazhar) — with
layout="{table_name}"the prefix is nowevent.rather thanevent, which is what stops areplaceofeventfrom deletingevents. A newwarn_unsafe_layout_separatorsoption (defaultTrue) warns when the separator around{table_name}can also occur inside table names; set it toFalseto keep a layout as it is.
Highlights
-
Cross-destination joins (#4286 @rudolfix) — join datasets that live on different destinations. dlt attaches the foreign dataset into the query engine of the destination you read from, so the join runs in a single engine:
users = duckdb_pipeline.dataset().table("users") orders = s3_pipeline.dataset().table("orders") joined = users.join(orders, on="users.id = orders.user_id")
Supported for
duckdb,motherduck,ducklake,lance,lancedbandfilesystem, with eager and lazy materialization -
Snowflake nested types (#4276 @rudolfix) — native nested-type support behind a new
use_nested_typesflag: automatic for parquet with nested fields, and for jsonl/dicts with an upfrontcolumnsdefinition via a pyarrow schema. Schema and data-type evolution both work. The user-facing interface piggy-backs on thejsondata type and is experimental for now. -
Input/output lineage in traces (#4289 @rudolfix) — traces now carry lineage in a relational form close to OpenLineage but ingestible by dlt itself. Inputs (per resource and data location) land on the
extractstep, and dlt derives the outputs onto theloadstep. -
Manual load package abort (#3557 @anuunchin) —
abort_packagesrecords what happened instead of silently deleting, and gives you the chance to inspect or retry a failed job first. Addslist_pending_retry_jobs_in_package(),fail_pending_job()andretry_failed_job()onPipeline, adlt pipeline <name> load-package <load-id> fail-job <job>CLI command, and an.exceptionsfolder in the load package where every retry writes its exception message. See Breaking Changes for the behavior change this implies. -
Retryable schema migrations (#4266 @rudolfix) — a
retry_schema_updatehelper for use with tenacity, so a failing schema migration retries with jitter and backoff instead of failing the load:from tenacity import retry, retry_if_exception, stop_after_attempt, wait_random_exponential from dlt.pipeline.helpers import retry_schema_update @retry( stop=stop_after_attempt(5), wait=wait_random_exponential(multiplier=1, max=30), retry=retry_if_exception(retry_schema_update()), reraise=True, ) def load(): return pipeline.run(chess_source(...))
Core Library
- Cross-destination joins (#4286 @rudolfix) — see Highlights.
- Snowflake nested types (#4276 @rudolfix) — see Highlights.
- Input/output lineage in traces (#4289 @rudolfix) — see Highlights.
- Manual load package abort (#3557 @anuunchin) — see Highlights.
- Retryable schema migrations (#4266 @rudolfix) — see Highlights. Also stops creating comments on
_dlttables on databricks, which madeCREATE TABLEnon-atomic, and adds an option to disable comment creation altogether via TBLPROPS. - Configurable destination session time zone (#4330 @rudolfix) —
session_timezonecan be set on clickhouse, databricks, duckdb, ducklake, postgres, redshift, snowflake and others, guarded by a newsupports_session_timezonecapability. Defaults are unchanged:UTCon duckdb and snowflake, where snowflake's was previously hardcoded. duckdb'sTimeZonemoves from global to per-connection config, so dlt no longer changes the global timezone of a duckdb connection you pass in; setsession_timezoneto an empty string to keep the duckdb default of the machine timezone. add_limiton the source factory (#4223 @richacode007-byte) —@dlt.sourcefactories accept.add_limit()before instantiation, for both sync and async sources, and return the factory for chaining. Factory clones preserve the limit.- Profile and config-value location in traces (#4303 @rudolfix) — the run context in a trace now reports the active profile, and the resolver trace records where each config or secret value came from, down to the exact profile file (
dev.secrets.toml), vault identity or AWS account, region and prefix. See Breaking Changes for the trace shape change. instancekey in the jobrequirespec (#4262 @tetelio) — a job can declare its runner instance requirements as an open dict, withinstance.size(for example{"size": "medium"}) read today. The legacymachinekey still works and is still serialized, but now emits aDltDeprecationWarning, so existing manifests and deployments are unaffected.- Readable
__repr__forWorkspaceRunContext(#4022 @sohamwaghe) — shows the run dir and profile instead of an opaque object in a REPL or debugger. - Fix: include
initialize_storagein schema-update error handling (#4325 @rudolfix) —CREATE SCHEMAwas excluded from the retry-only-the-schema-update path. - Fix: add a dot separator to
get_table_prefix_layout(#4283 @AnnasMazhar) — stops areplaceofeventfrom deletingevents. See Breaking Changes for the delta and iceberg table move and the newwarn_unsafe_layout_separatorsoption. - Fix: preserve the import schema after a restore (#4251 @AkhilTrivediX) — schemas restored from a destination were linked to the current import schema hash without containing the imported changes, so the next load skipped the import.
- Fix: migrate stored schemas read from a destination (#4239 @bartcode) — four raw-JSON loaders used
Schema.from_stored_schema, which skipsmigrate_schemaandapply_defaults.dlt.dataset()and the dashboard schema-version loader therefore could not read schemas written by older dlt versions, raisingKeyError: 'name'on any naming normalization. - Fix: build boolean sqlglot literals as
sge.Boolean(#4231 @chuenchen309) —boolfell into theint/floatbranch and produced a malformed numeric literal that crashedannotate_types()withdecimal.InvalidOperationinRelation.where()and.filter(). - Fix: keep identifiers case-sensitive in the sqlglot schema (#4269 @rudolfix) — sqlglot 30.13.0 started normalizing identifiers in more cases.
- Fix: pass the load-id column name to
remove_columnsas a sequence (#4241 @chuenchen309) —add_dlt_load_id_columnsilently droppedRecordBatchcolumns whose name is a substring of_dlt_load_id(id,load_id,load,dlt, …), because membership against a barestris a substring test. - Fix: quote stage references and file paths in Snowflake local file loads (#4281 @burnash)
- Fix: default the athena dbt
work_grouptoprimary(#4282 @burnash) — newer dbt-athena passes an empty value straight through. - Fix: apply
extra_placeholdersinget_table_prefix(#4226 @axelray-dev) — whenextra_placeholdersoverrode{schema_name}, replace-truncation and table listing matched zero files and silently left old data in place. - Fix: append the extension when a filesystem layout omits
{ext}(#4220 @mattfaltyn) — see Breaking Changes for the resulting file name change. - Fix: allow JWT auth without scopes (#4235 @mattfaltyn) —
OAuthJWTAuth.scopesis optional and defaults toNone, but__post_init__passed it tostr.join, raisingTypeErrorbefore authentication could begin. - Fix: handle a non-scalar total in the range paginator (#4186 @anxkhn) —
int(total)raisesTypeError, notValueError, whentotal_pathresolves to an object or array, so pagination failed opaquely instead of raising the paginator's actionable error. - Fix: preserve paginator stop conditions (#4227 @mattfaltyn) — an API returning
has_more=truecould override a stop already decided bymaximum_offset,maximum_pageor the response total, or re-enable a cursor paginator with no valid cursor.has_more=falsecan still stop pagination early. - Fix: remove
ModuleType.__dict__usage infringing PEP 562 (#4215 @zilto) - Warn about ephemeral storage in load package errors (#4211 @tetelio) — the error now says that a machine with ephemeral storage will not retry the package on the next run, because the files are gone from disk.
Docs
- AI Harness section (#4270 @ShreyasGS) — new hub pages covering what the AI Harness is and its four artifact kinds, a catalog of the shipped toolkits mapped to the ingest → validate → transform → deploy → observe lifecycle, and installation with
uvx dlthub-init@latest. - Inviting members, org and workspace roles (#4082 @sh-rp) — invite-by-email and accept-on-sign-in, revoking pending invites, the full role model including the organization
guestand workspacedeveloperroles, how invites and roles combine, and the safety rules for managing members. - Instance size in job configuration (#4293, #4292 @tetelio) — configuring a job's instance size, including disk, with a pointer to optimizing dlt.
- Instance-size preview banner, platform-tutorial removed (#4294 @ShreyasGS) — deletes
hub/getting-started/platform-tutorial.mdand repoints the redirects atpipeline-operations/deploymentsso old URLs keep resolving. - README refresh (#4083 @elviskahoro) — rewritten as a feature tour with runnable code: a no-auth
rest_api_sourcedoing the full describe → load →dataset().pokemon.df()round trip, plussql_database,filesystem | read_csv_duckdband direct pandas/Polars/Arrow. Meta sections are kept intact. - Replace the dead playground link in the README (#4267 @elviskahoro) — points at the dlt + Hugging Face marimo notebook on molab alongside the existing Colab demo.
- Revised contribution guidelines and a new AI-assisted policy (#4252 @burnash) — a shorter, more predictable path from "I want to help" to "merged", steering new contributors to issues labelled
help wantedorgood first issue. - Remove an outdated IP from the docs (#4238 @VioletM)
- Fix typos in comments, docstrings and docs (#4189 @maxtaran2010)
Chores
- agentic-docs workflow (#4314 @ShreyasGS) — the trigger for the bot that drafts documentation pages from labelled issues and revises them from review feedback. The workflow only checks an allowlist and starts a Cloud Run job, so only numeric IDs cross the boundary and no external text passes through a shell step.
- Do not start an agentic-docs run for the bot's own comments (#4323 @ShreyasGS) — the bot's own
/positionexample in an issue comment matched the workflow's trigger and the run began writing the wrong page. - Arm the agentic-docs run on the agent label, not the label set (#4338 @ShreyasGS) — applying
documentationandagenttogether sends twolabeledwebhooks and both matched.agentnow arms the run, and the/positioncomment arm is removed. - Preserve dependency resolution across test jobs (#4264 @Travior) — propagates matrix resolution settings through
UV_SYNC_ARGSand stopsuv runfrom resyncing prepared CI environments viaUV_NO_SYNC. Also raises the GitPython minimum and keeps the SQLGlot compatibility test working across supported versions. - Rerun remote destinations on transient connection errors (#4243 @burnash) — widens the
pytest-rerunfailuresfilters for mssql, synapse and fabric ODBC transients and for databricksDatabaseTransientException. Benign assertion failures still fail fast. - Stop the flaky
gather_metricstest on an early sleep return (#4236 @burnash)
New Contributors
- @sohamwaghe made their first contribution in #4022
- @anxkhn made their first contribution in #4186
- @maxtaran2010 made their first contribution in #4189
- @mattfaltyn made their first contribution in #4220
- @richacode007-byte made their first contribution in #4223
- @axelray-dev made their first contribution in #4226
- @chuenchen309 made their first contribution in #4231
- @bartcode made their first contribution in #4239
- @AkhilTrivediX made their first contribution in #4251