Skip to main content

Local-first ETL/ELT pipelines that run on DuckDB. CLI, Python API, and MCP server for AI agents.

Project description

Duckle

Local-first ETL/ELT pipelines that run on DuckDB. Python builds the plan, DuckDB moves the rows. Your data never leaves the machine.

PyPI License GitHub

Build your first pipeline

Paste this into Claude Code, Cursor, or Codex. The agent does the rest.

Run uvx duckle quickstart to build my first pipeline and run it

Nothing to install first. The agent fetches Duckle and the DuckDB engine on demand, runs a real pipeline, and shows you the rows.

Prefer to drive it yourself:

uvx duckle quickstart      # no install
pip install duckle         # or install it
Terminal: uvx duckle quickstart scaffolds sample data and a pipeline, runs it, and prints the resulting rows

quickstart scaffolds sample data and a pipeline, runs it, and shows you the rows. One command from nothing to a result, because the engine comes with it: a ~20 MB native binary plus the DuckDB CLI. No JVM, no Docker, no server, no account.


No data passes through Python

Every method appends a node to a pipeline graph. Nothing executes until .run(), and then the whole graph is handed to the engine, compiled to SQL, and executed inside DuckDB.

That is the difference from a dataframe library: a billion-row job costs what the SQL costs, because it is SQL. There is no interpreter in the data path, and no to_pandas() escape hatch quietly pulling rows into memory.

import duckle
from duckle import col

(duckle.read_csv("orders.csv")
    .where(col.amount >= 20)
    .derive(total="round(amount * 1.2, 2)", tag="f'{region}-{id}'")
    .write_parquet("out.parquet")
    .run())

Python expressions, compiled to SQL

amount * 1.2, f'{a}-{b}', x if c else y, name.strip().upper() and email is None are translated to vectorized DuckDB SQL at plan time:

.derive(band="'high' if amount > 50 else 'low'")
# CASE WHEN ("amount" > 50) THEN 'high' ELSE 'low' END

An expression with no exact SQL equivalent is rejected with the construct named, never quietly executed somewhere slower. Comprehensions, lambda, indexing and eval are refused by name.

Prefer editor completion over strings? col builds the same tree with real operators, and fragments are reusable:

eu_only = col.region.isin(["EU", "UK"])
p.where(eu_only & (col.amount >= 20))

See exactly what will run, before it runs:

p.explain()   # prints the compiled SQL, one block per stage

359 components, not 10 file formats

Component ids map onto attribute paths, so anything in the catalog is reachable:

duckle.src.salesforce(object="Account", authMode="clientCredentials")
duckle.xf.geo.reproject(geomColumn="geom", targetCrs="EPSG:3857")
duckle.snk.salesforce.bulk(operation="upsert", externalIdField="Ext__c")

104 sources, 66 sinks and 138 transforms: Postgres, MySQL, Oracle, SQL Server, Snowflake, Databricks, Teradata, SAP OData, Salesforce (including Bulk API 2.0), Kafka, WebSocket, S3, SFTP, IMAP, LanceDB and more.

duckle.component_ids(contains="salesforce")
duckle.describe("snk.salesforce.bulk")   # settings, and which ones the engine ignores

A CI gate that needs no engine

Terminal: duckle validate reports one failing and one passing pipeline, then exits 1

duckle validate compiles every pipeline without opening a source or writing a sink, so it needs no DuckDB, no credentials and no network. Exit codes are stable and safe to gate on:

code meaning
0 clean
1 a real finding: a pipeline failed, or did not compile
2 the runner could not start: bad usage, missing engine
duckle validate --json          # machine-readable, for a build step
duckle --pipeline my.json       # run one

Note: validate does not yet catch every missing required property, so a clean validate is not proof that a run will succeed.

Agent-ready, with nothing installed

Terminal: adding duckle as an MCP server via uvx, then an agent discovering Salesforce connectors, reading a component schema, creating a pipeline that compiles, and running it

The same package is an MCP server, so an AI agent gets a governed way to work with data instead of a shell. Point Claude Code, Claude Desktop or Cursor at it:

{ "mcpServers": { "duckle": { "command": "uvx", "args": ["duckle", "mcp"] } } }

Or in Claude Code:

claude mcp add duckle -- uvx duckle mcp

That is the whole setup. No pip install, no PATH, no engine to configure: uv fetches the package and the DuckDB engine into a throwaway environment and the server finds it there. If you did pip install duckle, "command": "duckle", "args": ["mcp"] works the same way.

19 tools, including list_components, get_component_schema, create_pipeline, validate_pipeline, run_pipeline, pipeline_lineage, trust_report and schema_drift.

What that buys over letting an agent write a script: it can discover a real connector rather than guess one, compile-check a pipeline before anything executes, run it, and get column-level lineage back. validate_pipeline opens no source and writes no sink, so an agent can check its work without touching your data. The pipeline it produces is the same JSON your desktop canvas opens, so you can inspect what it built.

Code and canvas are the same file

Pipelines are the same JSON the Duckle desktop studio reads. A pipeline written in Python opens on the canvas, and one drawn on the canvas runs from Python:

p.save("pipelines/orders.json")          # opens in the studio
duckle.from_json("pipelines/orders.json").run()

Install notes

duckle depends on duckdb-cli, published by the DuckDB Foundation, so the engine arrives with the install and works offline. To pin your own build instead, set DUCKLE_DUCKDB_BIN.

Wheels ship for Linux, macOS and Windows on x86-64 and arm64. The wheel carries a compiled Rust binary and is Python-version independent (py3-none-<platform>).


Apache-2.0  ·  GitHub  ·  duckle.org  ·  Issues

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

duckle-0.5.9-py3-none-win_arm64.whl (21.8 MB view details)

Uploaded Python 3Windows ARM64

duckle-0.5.9-py3-none-win_amd64.whl (23.0 MB view details)

Uploaded Python 3Windows x86-64

duckle-0.5.9-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (23.2 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

duckle-0.5.9-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl (22.3 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

duckle-0.5.9-py3-none-macosx_11_0_x86_64.whl (21.3 MB view details)

Uploaded Python 3macOS 11.0+ x86-64

duckle-0.5.9-py3-none-macosx_11_0_arm64.whl (20.5 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file duckle-0.5.9-py3-none-win_arm64.whl.

File metadata

  • Download URL: duckle-0.5.9-py3-none-win_arm64.whl
  • Upload date:
  • Size: 21.8 MB
  • Tags: Python 3, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for duckle-0.5.9-py3-none-win_arm64.whl
Algorithm Hash digest
SHA256 e1e2642445e82fd41c541cb3c9296f670663a84d9101ecce3a11a54f5b93bfcc
MD5 0c581608431073cb1b3c1f87cd71da5c
BLAKE2b-256 6fb726d8165dcdd19dbe25e28905a1508610ac4ede207849aa1688cfb4445ecb

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.9-py3-none-win_arm64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file duckle-0.5.9-py3-none-win_amd64.whl.

File metadata

  • Download URL: duckle-0.5.9-py3-none-win_amd64.whl
  • Upload date:
  • Size: 23.0 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for duckle-0.5.9-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 bdc593de329f2f01523485a38dd442b6265442f041af2e2e5353e2a002df636f
MD5 890b23ec752e376fb0e34e6d05cd3dff
BLAKE2b-256 c1bc188169ccc16a4df344b0d82cc8c60f32e8780e3e38ae86c60d365cabea69

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.9-py3-none-win_amd64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file duckle-0.5.9-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for duckle-0.5.9-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 f9e366dfd5dcffa0ccdb464998d5ee9a2170f16890c5f2ffbc431c417bf4dd6d
MD5 414076e556fea98e9f98a124b5d51ccb
BLAKE2b-256 aa68b9c8ff85553fb1fbe2537d7599605a5727e0ea260cac466c89b897bfce88

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.9-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file duckle-0.5.9-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl.

File metadata

File hashes

Hashes for duckle-0.5.9-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 ef3fc5f2c9f9092c12e996fab85456b2ba4ccc9a15d9a052a80e7948e6d63647
MD5 039abe39ae42cbb64c420ae25945709f
BLAKE2b-256 bb2aa0d65b2028687b945705ff3c7a108eb7c29f349cf84efe60aea4883bd1f8

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.9-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file duckle-0.5.9-py3-none-macosx_11_0_x86_64.whl.

File metadata

File hashes

Hashes for duckle-0.5.9-py3-none-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 a836b886fe984c2092d1a89f303618f4b2955b4f824191906ffc9bb9bcee12e2
MD5 333e3cd126d07e0492513193e9652e03
BLAKE2b-256 91f4072a0e1d6d71d647894c79cedd285bf8d31a57df686ac82726710ea509ba

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.9-py3-none-macosx_11_0_x86_64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file duckle-0.5.9-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for duckle-0.5.9-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 3a3c8829d06126e6a09a5570c860e0859b9ee0d45bd04a873614153c443cb8cf
MD5 4edd3127c39a2537013d848031f44fe2
BLAKE2b-256 1f0ae1177737712902c87b8c44c6b94e4deff178deb94e32c7f7518d27722840

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.9-py3-none-macosx_11_0_arm64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page