Beta — API stabilizing toward 1.0

All-in-One Data Pipeline Engine

A Python framework for data pipelines — batch or streaming, scheduled or event-driven — with a fast query engine and an operator Web UI built in. One pip install, no cluster to run.

No tuning required. The fastest engine we've measured on single or multi node.

Faster

Distributed, TPC-H SF=100

8× faster than Trino on the same 4-node cluster.

ematix-flow (auto mesh) 61.9 s
Trino 497 s
PySpark DNF — 18/22 queries failed

4× c7i.4xlarge · SF=10: 5.6× vs Trino, 6.5× vs PySpark · SF=1: 7.2× / 19×

Cheaper

One node vs their cluster, SF=10

One ematix node outruns a 4-node Trino cluster 12×.

ematix-flow · 1 node 4.8 s
Trino · 4 nodes 56.4 s
PySpark · 4 nodes 65.0 s

Same queries, same S3 parquet — a quarter of the hardware, ~12–14× the speed

Simpler

Single node, pip install, zero tuning

Fastest single-node engine at every scale we measured.

ematix-flow 4.8 s
DuckDB 5.8 s
Polars SF=1 only: 2.45 s vs ematix 0.68 s

SF=100 totals: ematix 51.8 s, DuckDB 59.0 s · SF=1: 0.68 s vs 1.45 s

Measured on AWS, not a laptop — TPC-H on c7i.4xlarge (16 vCPU / 32 GB), July 2026, shipped pip install ematix-flow defaults, no flags, results value-validated. Full benchmarks →

Documentation

Start the tutorial →

Featured concepts

All concepts →

Quick peek

Full tutorial →

A workflow with a composite trigger (event + cron) plus within-DAG ordering.

from ematix_flow import ematix, ManagedTable, Annotated, BigInt, Text, pk

@ematix.connection
class warehouse:
    kind = "postgres"
    url = "${WAREHOUSE_URL}"

class OrdersExtracted(ManagedTable):
    __schema__ = "analytics"; __tablename__ = "orders_extracted"
    order_id: Annotated[BigInt, pk()]
    customer_id: BigInt
    amount_cents: BigInt

class OrdersEnriched(ManagedTable):
    __schema__ = "analytics"; __tablename__ = "orders_enriched"
    order_id: Annotated[BigInt, pk()]
    amount_bucket: Text

@ematix.job(name="extract_orders",
            target=OrdersExtracted, target_connection="warehouse",
            mode="merge", keys=("order_id",))
def extract_orders(conn):
    return "SELECT order_id, customer_id, amount_cents FROM raw.orders"

@ematix.job(name="enrich_orders",
            target=OrdersEnriched, target_connection="warehouse",
            mode="merge", keys=("order_id",),
            depends_on=["extract_orders"])
def enrich_orders(conn):
    return "SELECT order_id, CASE WHEN amount_cents < 10000 THEN 'small' ELSE 'large' END AS amount_bucket FROM analytics.orders_extracted"

# Workflow declares the trigger; member jobs declare their DAG position.
ematix.workflow(
    name="orders_etl",
    triggered_by=["upstream_workflow"],
    schedule="0 21 * * *",
    timezone="America/New_York",
    jobs=["extract_orders", "enrich_orders"],
)
Currently in beta

On PyPI as ematix-flow. The core API is settling toward 1.0; pinning a minor version is still recommended. Bug reports and design pushback during the beta window are exactly what we want — file issues on GitHub.