Skip to content

Delta Lake

DeltaIOHandler

Persists asset outputs as Delta Lake tables.

from rivers.io_handlers.delta import DeltaIOHandler

io = DeltaIOHandler(
    table_uri="/data/delta",
    mode="overwrite",
)

Constructor:

Parameter Type Default Description
table_uri str required Base URI for Delta tables. Each asset creates a subdirectory.
mode str "overwrite" Write mode: "overwrite", "append", "error", "ignore", "merge", "create_or_replace".
schema_mode str \| None None Schema evolution: "overwrite" or "merge".
storage_options dict[str, str] \| None None Credentials for remote storage.
writer_properties WriterProperties \| None None Parquet writer settings.
commit_properties CommitProperties \| None None Delta commit settings.
table_config dict[str, str] \| None None Delta table properties.
merge_config MergeConfig \| None None Merge configuration (required when mode="merge").
handler_config dict[str, Any] \| None None Useful to pass handler-related custom config (e.g. a pre-initialized SparkSession object, or a DuckDBResource as {"duckdb": ...})

Asset metadata overrides:

These metadata keys override handler defaults per-asset:

Key Type Description
delta/mode str Write mode override.
delta/schema_mode str Schema mode override.
delta/partition_expr str \| JSON dict Partition column mapping.
delta/table_configuration JSON str Table properties override.
delta/writer_properties JSON str Writer properties override.
delta/commit_properties JSON str Commit properties override.
delta/merge_predicate str Override merge predicate for this asset.
delta/columns JSON list Column selection for reads.
delta/version str Table version for time travel reads.

Output metadata:

Key Type Description
delta/table_uri str Full table URI.
delta/mode str Write mode used.
delta/num_rows int Total rows in table after write.
delta/size_bytes int Total table size in bytes.
delta/write_duration_s float Write duration in seconds.
delta/version int Delta table version after write.
rivers/schema Schema Arrow schema of the written table.

Methods for action bodies

An action receives the asset's resolved handler as ctx.io_handler. These methods expose the same URI and partition resolution the write path uses, so a delete/optimize body targets exactly the rows the materialize path would have written.

asset_table_uri(asset_name, asset_metadata=None)

Returns {table_uri}/{leaf}, honoring a delta/root_name override in asset_metadata. Pass ctx.asset_name and ctx.asset_metadata.

@rs.action(outcome=rs.Outcome.Unchanged)
@classmethod
def optimize(cls, ctx: rs.ActionContext) -> None:
    uri = ctx.io_handler.asset_table_uri(ctx.asset_name, ctx.asset_metadata)
    DeltaTable(uri, storage_options=ctx.io_handler.storage_options).optimize.compact()

partition_predicate(asset_metadata, partition)

Returns a SQL predicate covering the partition(s), honoring delta/partition_expr. Pass ctx.asset_metadata and ctx.partition.

@rs.action(outcome=rs.Outcome.Unmaterialize)
@classmethod
def delete(cls, ctx: rs.ActionContext) -> None:
    uri = ctx.io_handler.asset_table_uri(ctx.asset_name, ctx.asset_metadata)
    predicate = ctx.io_handler.partition_predicate(ctx.asset_metadata, ctx.partition)
    DeltaTable(uri, storage_options=ctx.io_handler.storage_options).delete(predicate)

partition_filters(asset_metadata, partition)

Returns delta-rs partition_filters for the partition(s), honoring delta/partition_expr: a list of (column, op, value) conjunctions, one per partition key. optimize.compact and optimize.z_order take one conjunction per call.

@rs.action(outcome=rs.Outcome.Unchanged)
@classmethod
def compact(cls, ctx: rs.ActionContext) -> None:
    uri = ctx.io_handler.asset_table_uri(ctx.asset_name, ctx.asset_metadata)
    table = DeltaTable(uri, storage_options=ctx.io_handler.storage_options)
    for filters in ctx.io_handler.partition_filters(ctx.asset_metadata, ctx.partition):
        table.optimize.compact(partition_filters=filters)

partition is required: reach these only from a keyed action run. On a non-partitioned asset, delete the whole table instead of building a predicate.


DeltaAsset

Asset base class with the standard Delta maintenance verbs built in. Subclass it, define materialize, and optimize, vacuum, and delete appear as actions, resolved against the asset's DeltaIOHandler (its own io_handler or the repository default). Sets kinds = "delta".

from rivers.io_handlers.delta import DeltaAsset  # also exported as rs.DeltaAsset

class Orders(DeltaAsset):
    io_handler = WAREHOUSE

    @classmethod
    def materialize(cls) -> pl.DataFrame: ...
Verb Declaration Behavior
optimize Unchanged + Exclusive + Optional key optimize.compact(), or z_order when z_order_by is configured. With a key, rewrites only that partition's files via partition_filters; without one, the whole table. From the UI or gRPC, the keyless form needs an explicit whole-asset choice.
vacuum Unchanged + Exclusive + Keyless Removes files no longer referenced by the table. Table-wide: runs without a partition key on partitioned assets too; a supplied key is rejected.
delete Unmaterialize + Exclusive + DownstreamFirst + Optional key Deletes the keyed partition's rows via partition_predicate, or every row without a key — on partitioned assets both forms are valid. From the UI or gRPC, the keyless form needs an explicit whole-asset choice.

A verb requires the asset to resolve a DeltaIOHandler — anything else fails the step with a TypeError naming the requirement. On a table that does not exist yet, optimize and vacuum report ActionResult.unchanged() instead of failing, so a fleet-wide repo.run_action("optimize") skips never-materialized assets; delete still applies its Unmaterialize outcome, so dangling state clears even when the physical table is already gone. A subclass redefining a verb replaces the built-in.

OptimizeConfig

Per-asset overrides via run_action("optimize", config={"assets": {"<asset>": {"config": {...}}}}):

Field Type Default Description
target_size int \| None None Desired file size in bytes (None uses the deltalake default).
z_order_by list[str] \| None None Z-order by these columns instead of plain compaction.

VacuumConfig

Field Type Default Description
retention_hours int \| None None Files older than this are removed (None uses the table's retention, 168h by default).
enforce_retention_duration bool True Refuse retention windows shorter than the table's configured minimum.

DeltaTypeHandler

Parent abstract base for adding type support to DeltaIOHandler.

from rivers.io_handlers.delta.base import DeltaTypeHandler

class MyTypeHandler(DeltaTypeHandler[MyType]):
    @property
    def supported_types(self) -> Sequence[type[MyType]]:
        return [MyType]

    def load_input(self, table_uri, storage_options, predicate,
                   target_type, columns=None, version=None) -> MyType:
        ...

    def handle_output(self, context: OutputContext,
                    obj: T, request: DeltaWriteRequest):
        ...

Abstract members:

Member Description
supported_types Property returning list of types this handler supports.
load_input(...) Load data from a Delta table.
handle_output(...) Write data to a Delta table.

Built-in type handler subclasses:

Type Handler Class Description
ArrowDeltaTypeHandler For Arrow-based type support.
PySparkDeltaTypeHandler For Spark-based types support.

ArrowDeltaTypeHandler

DeltaTypeHandler-based abstract handler class for adding Arrow-based type support to DeltaIOHandler. Writes with handle_output are pre-implemented using deltalake (delta-rs).

from rivers.io_handlers.delta.base import ArrowDeltaTypeHandler

class MyTypeHandler(ArrowDeltaTypeHandler[MyType]):
    @property
    def supported_types(self) -> Sequence[type[MyType]]:
        return [MyType]

    def to_arrow(self, obj: T) -> RecordBatchReader:
        ...

    def load_input(self, table_uri, storage_options, predicate,
                   target_type, columns=None, version=None) -> MyType:
        ...

    def handle_output(self, context: OutputContext,
                    obj: T, request: DeltaWriteRequest):
        ...

Abstract members:

Member Description
supported_types Property returning list of types this handler supports.
to_arrow(obj) Convert object to arro3.core.RecordBatchReader.
load_input(...) Load data from a Delta table.

Built-in handlers (auto-registered when their library is installed):

Handler Module Types
PyArrowTypeHandler rivers.io_handlers.delta.pyarrow pyarrow.Table, pyarrow.RecordBatchReader
PolarsTypeHandler rivers.io_handlers.delta.polars polars.DataFrame, polars.LazyFrame
PandasTypeHandler rivers.io_handlers.delta.pandas pandas.DataFrame
DataFusionTypeHandler rivers.io_handlers.delta.datafusion datafusion.DataFrame
DuckDBTypeHandler rivers.io_handlers.delta.duckdb duckdb.DuckDBPyRelation

PySparkDeltaTypeHandler

DeltaTypeHandler-based handler class for adding Spark-based type support to DeltaIOHandler. Reads with load_input and writes with handle_output, both are implemented with Spark.

from rivers.io_handlers.delta.pyspark import PySparkDeltaTypeHandler

class MyTypeHandler(PySparkDeltaTypeHandler[MyType]):
    @property
    def supported_types(self) -> Sequence[type[MyType]]:
        return [MyType]

    def load_input(self, table_uri, storage_options, predicate,
                   target_type, columns=None, version=None) -> MyType:
        ...

    def handle_output(self, context: OutputContext,
                    obj: T, request: DeltaWriteRequest):
        ...

PartitionExpr

Maps partition dimensions to Delta table column names.

from rivers.io_handlers.delta import PartitionExpr

# Single dimension
expr = PartitionExpr(expr="date")

# Multi-dimensional
expr = PartitionExpr(expr={"date": "event_date", "region": "region_code"})

Attributes:

Attribute Type Description
expr str \| dict[str, str] Column name or dimension-to-column mapping.

Properties:

Property Type Description
partition_columns list[str] List of Delta column names.

MergeConfig

Configuration for MERGE INTO operations.

from rivers.io_handlers.delta import MergeConfig

config = MergeConfig(
    merge_type="upsert",
    predicate="s.id = t.id",
    source_alias="s",
    target_alias="t",
)

Attributes:

Attribute Type Default Description
merge_type str required One of: "deduplicate_insert", "update_only", "upsert", "replace_delete_unmatched", "custom".
predicate str required SQL merge condition.
source_alias str "s" Alias for the source table.
target_alias str "t" Alias for the target table.
error_on_type_mismatch bool True Fail if source/target schemas differ.
operations MergeOperationsConfig \| None None Required when merge_type="custom".

MergeOperationsConfig

Fine-grained control over MERGE clauses.

Attributes:

Attribute Type
when_not_matched_insert list[WhenNotMatchedInsert] \| None
when_not_matched_insert_all list[WhenNotMatchedInsertAll] \| None
when_matched_update list[WhenMatchedUpdate] \| None
when_matched_update_all list[WhenMatchedUpdateAll] \| None
when_matched_delete list[WhenMatchedDelete] \| None
when_not_matched_by_source_delete list[WhenNotMatchedBySourceDelete] \| None
when_not_matched_by_source_update list[WhenNotMatchedBySourceUpdate] \| None

Merge operation classes

WhenNotMatchedInsert

Attribute Type
predicate str \| None
updates dict[str, str]

WhenNotMatchedInsertAll

Attribute Type
predicate str \| None
except_cols list[str] \| None

WhenMatchedUpdate

Attribute Type
predicate str \| None
updates dict[str, str]

WhenMatchedUpdateAll

Attribute Type
predicate str \| None
except_cols list[str] \| None

WhenMatchedDelete

Attribute Type
predicate str \| None

WhenNotMatchedBySourceDelete

Attribute Type
predicate str \| None

WhenNotMatchedBySourceUpdate

Attribute Type
predicate str \| None
updates dict[str, str]